mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 18:17:18 +10:00
Compare commits
272 Commits
copilot/im
...
unstable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b29fd3be4 | ||
|
|
016e5e1b12 | ||
|
|
92b3bde862 | ||
|
|
64f7349fca | ||
|
|
527372ba74 | ||
|
|
c6c07cb52f | ||
|
|
913f033d1a | ||
|
|
688f8cc4ef | ||
|
|
de51879ae9 | ||
|
|
2943e8e5f0 | ||
|
|
e2b2af8322 | ||
|
|
c8d8d0a3e7 | ||
|
|
8cd7713ca9 | ||
|
|
566abb00cd | ||
|
|
ae7550b465 | ||
|
|
63d4cdffef | ||
|
|
5516d7b045 | ||
|
|
c639c27cdb | ||
|
|
f0022f59a2 | ||
|
|
9e7d863ee7 | ||
|
|
d5c6c6aed2 | ||
|
|
4d89d732e2 | ||
|
|
f6821be8a3 | ||
|
|
03b01efe49 | ||
|
|
16aeba8ec0 | ||
|
|
283a5aacee | ||
|
|
8d852bba9b | ||
|
|
29c8794f45 | ||
|
|
c8d593503f | ||
|
|
a8934be7cd | ||
|
|
7aef716ebc | ||
|
|
7df171ff20 | ||
|
|
46eda3e96f | ||
|
|
727a9d18d6 | ||
|
|
20f60b8c7b | ||
|
|
84b0ddff7f | ||
|
|
811ea13b73 | ||
|
|
bdb90f0a01 | ||
|
|
c9ab6458fa | ||
|
|
16a249f672 | ||
|
|
54468a1a2a | ||
|
|
8289bbd846 | ||
|
|
49c450d942 | ||
|
|
a7ee943216 | ||
|
|
8bb4c4dd32 | ||
|
|
67621ee6ba | ||
|
|
a09ffe6a0f | ||
|
|
e0be8743f6 | ||
|
|
0b04528803 | ||
|
|
65875e6dac | ||
|
|
4d6fb1d38d | ||
|
|
305b930d90 | ||
|
|
bc3884ca91 | ||
|
|
df0bf927e4 | ||
|
|
efe20ea51c | ||
|
|
e21a72fcd1 | ||
|
|
e1477bd065 | ||
|
|
aa495fce38 | ||
|
|
9cd60c28c0 | ||
|
|
2ba896c5ac | ||
|
|
1d388547ee | ||
|
|
e343cec4d5 | ||
|
|
d58efc5d01 | ||
|
|
4b26ab16fb | ||
|
|
0e27312eda | ||
|
|
4e0a953b98 | ||
|
|
27c5b0b1af | ||
|
|
84019b06d9 | ||
|
|
7fd21f8bf4 | ||
|
|
88695b0d1f | ||
|
|
fb269c9032 | ||
|
|
e62dc7bfa2 | ||
|
|
f295e195b5 | ||
|
|
ab76062a41 | ||
|
|
d14417d392 | ||
|
|
96c5c27610 | ||
|
|
91f92bee49 | ||
|
|
1803471e02 | ||
|
|
3de56d344e | ||
|
|
c71abbdfb8 | ||
|
|
ed15121e95 | ||
|
|
46c6945da5 | ||
|
|
1beb4cb002 | ||
|
|
4c65fea1ac | ||
|
|
8ae93a98e5 | ||
|
|
6da7e538e1 | ||
|
|
13e6ba4cb2 | ||
|
|
93b7328c3f | ||
|
|
11dc5bcbe1 | ||
|
|
fa3ab87b11 | ||
|
|
9bd9e9a58b | ||
|
|
9d6dee7451 | ||
|
|
9c2cdc7203 | ||
|
|
65150f5cc3 | ||
|
|
21a1512e6c | ||
|
|
cf4791f1ad | ||
|
|
0bc66e5a56 | ||
|
|
d48236da94 | ||
|
|
4c05d7b888 | ||
|
|
94ed42caf1 | ||
|
|
e0c18cc3d4 | ||
|
|
0817c25f4c | ||
|
|
7745a97cca | ||
|
|
9bcd715d31 | ||
|
|
6a95c66bc7 | ||
|
|
b5800847ae | ||
|
|
aa85cbb86e | ||
|
|
c59991420e | ||
|
|
c0304b8362 | ||
|
|
d1f1271a02 | ||
|
|
de4fdbe553 | ||
|
|
804606042f | ||
|
|
53f2db3f97 | ||
|
|
1f2fdec89d | ||
|
|
8714c157c9 | ||
|
|
657fba4ca5 | ||
|
|
0a69621207 | ||
|
|
58ccf82e0b | ||
|
|
ceab244329 | ||
|
|
58fcdceca2 | ||
|
|
98af3c0ad6 | ||
|
|
172a9d5e4e | ||
|
|
aba8346bd6 | ||
|
|
d8e269e0ac | ||
|
|
c45ea8dfac | ||
|
|
a2d313c59b | ||
|
|
15722b06dd | ||
|
|
d230dae0a5 | ||
|
|
e11dbf3a8e | ||
|
|
baa9f29f0d | ||
|
|
55b6e7dbfe | ||
|
|
a05e05a47c | ||
|
|
c1dc6cb0fb | ||
|
|
432fe1b3c9 | ||
|
|
8dd8897fd8 | ||
|
|
ff58edb1c1 | ||
|
|
79bab39502 | ||
|
|
a4d5d59901 | ||
|
|
1af14a0237 | ||
|
|
944a9986d9 | ||
|
|
60a1e4c866 | ||
|
|
5d67c131fa | ||
|
|
b9cc87d35a | ||
|
|
490d501257 | ||
|
|
725e4adc46 | ||
|
|
4a14d39cad | ||
|
|
8ec58c96f5 | ||
|
|
e8450b2e61 | ||
|
|
30c3855e4b | ||
|
|
ccf90aee8a | ||
|
|
e6c03fd448 | ||
|
|
e0f1cdf464 | ||
|
|
8d88c6532f | ||
|
|
3890bd2be7 | ||
|
|
6cd1eb9b94 | ||
|
|
f196b7a583 | ||
|
|
bd9935eebb | ||
|
|
0e0e838ff5 | ||
|
|
0caebd3171 | ||
|
|
7d2944eba9 | ||
|
|
a5db2feb5e | ||
|
|
708ceb3d29 | ||
|
|
157e33f2a4 | ||
|
|
1d4fb83313 | ||
|
|
85f5f6cebb | ||
|
|
6a750f4522 | ||
|
|
46c2cc37c3 | ||
|
|
aa8dd6e44f | ||
|
|
4e94a64dcc | ||
|
|
494990f914 | ||
|
|
95ccb837d3 | ||
|
|
24b33a43fc | ||
|
|
8ae16aa452 | ||
|
|
bf4a9edc89 | ||
|
|
78b4eac974 | ||
|
|
a34868468f | ||
|
|
e392c70b6f | ||
|
|
511d1bb3fa | ||
|
|
4273ffa77e | ||
|
|
f5ccf746ea | ||
|
|
b2d90b7d86 | ||
|
|
e0a78fde07 | ||
|
|
203f4134b0 | ||
|
|
c2b697a778 | ||
|
|
ddec2ab282 | ||
|
|
35ff7d1fb4 | ||
|
|
cba18635c8 | ||
|
|
0d8c7a9c5d | ||
|
|
faff3174a3 | ||
|
|
2fc1b672cc | ||
|
|
143983b585 | ||
|
|
4afdf4153a | ||
|
|
750dc9c3e0 | ||
|
|
48b7adde7d | ||
|
|
0585f6d065 | ||
|
|
8101a7b0bd | ||
|
|
e8620587dd | ||
|
|
a89680fa2d | ||
|
|
b919039c43 | ||
|
|
9b0960bb5a | ||
|
|
ad7b982242 | ||
|
|
7e68013b05 | ||
|
|
ac427b98f4 | ||
|
|
a5fb467db2 | ||
|
|
a930356b04 | ||
|
|
5bc0dfa9dd | ||
|
|
743b460e51 | ||
|
|
8d8ca282a1 | ||
|
|
cd56eaaba2 | ||
|
|
e92938364d | ||
|
|
1c4614318e | ||
|
|
0f5cda4169 | ||
|
|
d87c9fd242 | ||
|
|
fce21607bd | ||
|
|
3dc285be8c | ||
|
|
79bbce3db3 | ||
|
|
dfd95b2615 | ||
|
|
ab0869c972 | ||
|
|
9ac0539ffd | ||
|
|
cb4deb0c20 | ||
|
|
6b90b61358 | ||
|
|
ed1ee4c3a4 | ||
|
|
7f3ea8dbd8 | ||
|
|
12b055989b | ||
|
|
49056b5060 | ||
|
|
c530995832 | ||
|
|
60d81a73d9 | ||
|
|
e9c46cc359 | ||
|
|
9110851af3 | ||
|
|
107f92381b | ||
|
|
f84129ca79 | ||
|
|
44fafcef73 | ||
|
|
a5e09fcd43 | ||
|
|
387b42c9c2 | ||
|
|
044eb728cb | ||
|
|
2be8a45f14 | ||
|
|
1336987756 | ||
|
|
e3473d3de0 | ||
|
|
bba92146b1 | ||
|
|
48f84b31d6 | ||
|
|
1c846df903 | ||
|
|
0bd98a300f | ||
|
|
87eaf3ce6e | ||
|
|
239e6ec701 | ||
|
|
5be1887f92 | ||
|
|
65264afdf9 | ||
|
|
fecdbf20de | ||
|
|
1f03080540 | ||
|
|
737162e75a | ||
|
|
51ce402dbb | ||
|
|
8b404b5a4c | ||
|
|
3ce94d50dd | ||
|
|
29d56fca9c | ||
|
|
ab18010ee1 | ||
|
|
e69c202c79 | ||
|
|
0a812f2a46 | ||
|
|
fffe9fc566 | ||
|
|
6fdf27a701 | ||
|
|
7fa7d4f0a9 | ||
|
|
f511ebc1d4 | ||
|
|
84bbdc2eba | ||
|
|
568612fc70 | ||
|
|
d78828fd81 | ||
|
|
f56d9ab945 | ||
|
|
86fabd6a22 | ||
|
|
24a1e7cee4 | ||
|
|
223dd8bb1a | ||
|
|
68448de7d0 | ||
|
|
1ebff74c21 | ||
|
|
f0cd3422c1 | ||
|
|
e385a98ced | ||
|
|
670f32baee |
@@ -14,6 +14,7 @@
|
|||||||
--depends kmod-inet-diag
|
--depends kmod-inet-diag
|
||||||
--depends kmod-tun
|
--depends kmod-tun
|
||||||
--depends firewall4
|
--depends firewall4
|
||||||
|
--depends kmod-nft-queue
|
||||||
|
|
||||||
--before-remove release/config/openwrt.prerm
|
--before-remove release/config/openwrt.prerm
|
||||||
|
|
||||||
|
|||||||
23
.fpm_pacman
Normal file
23
.fpm_pacman
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
-s dir
|
||||||
|
--name sing-box
|
||||||
|
--category net
|
||||||
|
--license GPL-3.0-or-later
|
||||||
|
--description "The universal proxy platform."
|
||||||
|
--url "https://sing-box.sagernet.org/"
|
||||||
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
|
--config-files etc/sing-box/config.json
|
||||||
|
--after-install release/config/sing-box.postinst
|
||||||
|
|
||||||
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
|
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
||||||
|
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
||||||
1
.github/CRONET_GO_VERSION
vendored
Normal file
1
.github/CRONET_GO_VERSION
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ea7cd33752aed62603775af3df946c1b83f4b0b3
|
||||||
81
.github/build_alpine_apk.sh
vendored
Executable file
81
.github/build_alpine_apk.sh
vendored
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
ARCHITECTURE="$1"
|
||||||
|
VERSION="$2"
|
||||||
|
BINARY_PATH="$3"
|
||||||
|
OUTPUT_PATH="$4"
|
||||||
|
|
||||||
|
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
|
||||||
|
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
||||||
|
|
||||||
|
# Convert version to APK format:
|
||||||
|
# 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)
|
||||||
|
trap 'rm -rf "$ROOT_DIR"' EXIT
|
||||||
|
|
||||||
|
# Binary
|
||||||
|
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
|
||||||
|
|
||||||
|
# Config files
|
||||||
|
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
|
||||||
|
install -Dm755 "$PROJECT/release/config/sing-box.initd" "$ROOT_DIR/etc/init.d/sing-box"
|
||||||
|
install -Dm644 "$PROJECT/release/config/sing-box.confd" "$ROOT_DIR/etc/conf.d/sing-box"
|
||||||
|
|
||||||
|
# Service files
|
||||||
|
install -Dm644 "$PROJECT/release/config/sing-box.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box.service"
|
||||||
|
install -Dm644 "$PROJECT/release/config/sing-box@.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box@.service"
|
||||||
|
|
||||||
|
# Completions
|
||||||
|
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
|
||||||
|
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
|
||||||
|
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
|
||||||
|
|
||||||
|
# License
|
||||||
|
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
|
||||||
|
|
||||||
|
# APK metadata
|
||||||
|
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
|
||||||
|
mkdir -p "$PACKAGES_DIR"
|
||||||
|
|
||||||
|
# .conffiles
|
||||||
|
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
|
||||||
|
/etc/conf.d/sing-box
|
||||||
|
/etc/init.d/sing-box
|
||||||
|
/etc/sing-box/config.json
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# .conffiles_static (sha256 checksums)
|
||||||
|
while IFS= read -r conffile; do
|
||||||
|
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
|
||||||
|
echo "$conffile $sha256"
|
||||||
|
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
||||||
|
|
||||||
|
# .list (all files, excluding lib/apk/packages/ metadata)
|
||||||
|
(cd "$ROOT_DIR" && find . -type f -o -type l) \
|
||||||
|
| sed 's|^\./|/|' \
|
||||||
|
| grep -v '^/lib/apk/packages/' \
|
||||||
|
| sort > "$PACKAGES_DIR/.list"
|
||||||
|
|
||||||
|
# Build APK
|
||||||
|
apk mkpkg \
|
||||||
|
--info "name:sing-box" \
|
||||||
|
--info "version:${APK_VERSION}" \
|
||||||
|
--info "description:The universal proxy platform." \
|
||||||
|
--info "arch:${ARCHITECTURE}" \
|
||||||
|
--info "license:GPL-3.0-or-later with name use or association addition" \
|
||||||
|
--info "origin:sing-box" \
|
||||||
|
--info "url:https://sing-box.sagernet.org/" \
|
||||||
|
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
||||||
|
--files "$ROOT_DIR" \
|
||||||
|
--output "$OUTPUT_PATH"
|
||||||
80
.github/build_openwrt_apk.sh
vendored
Executable file
80
.github/build_openwrt_apk.sh
vendored
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
ARCHITECTURE="$1"
|
||||||
|
VERSION="$2"
|
||||||
|
BINARY_PATH="$3"
|
||||||
|
OUTPUT_PATH="$4"
|
||||||
|
|
||||||
|
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
|
||||||
|
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
||||||
|
|
||||||
|
# Convert version to APK format:
|
||||||
|
# 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)
|
||||||
|
trap 'rm -rf "$ROOT_DIR"' EXIT
|
||||||
|
|
||||||
|
# Binary
|
||||||
|
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
|
||||||
|
|
||||||
|
# Config files
|
||||||
|
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
|
||||||
|
install -Dm644 "$PROJECT/release/config/openwrt.conf" "$ROOT_DIR/etc/config/sing-box"
|
||||||
|
install -Dm755 "$PROJECT/release/config/openwrt.init" "$ROOT_DIR/etc/init.d/sing-box"
|
||||||
|
install -Dm644 "$PROJECT/release/config/openwrt.keep" "$ROOT_DIR/lib/upgrade/keep.d/sing-box"
|
||||||
|
|
||||||
|
# Completions
|
||||||
|
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
|
||||||
|
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
|
||||||
|
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
|
||||||
|
|
||||||
|
# License
|
||||||
|
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
|
||||||
|
|
||||||
|
# APK metadata
|
||||||
|
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
|
||||||
|
mkdir -p "$PACKAGES_DIR"
|
||||||
|
|
||||||
|
# .conffiles
|
||||||
|
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
|
||||||
|
/etc/config/sing-box
|
||||||
|
/etc/sing-box/config.json
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# .conffiles_static (sha256 checksums)
|
||||||
|
while IFS= read -r conffile; do
|
||||||
|
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
|
||||||
|
echo "$conffile $sha256"
|
||||||
|
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
||||||
|
|
||||||
|
# .list (all files, excluding lib/apk/packages/ metadata)
|
||||||
|
(cd "$ROOT_DIR" && find . -type f -o -type l) \
|
||||||
|
| sed 's|^\./|/|' \
|
||||||
|
| grep -v '^/lib/apk/packages/' \
|
||||||
|
| sort > "$PACKAGES_DIR/.list"
|
||||||
|
|
||||||
|
# Build APK
|
||||||
|
apk mkpkg \
|
||||||
|
--info "name:sing-box" \
|
||||||
|
--info "version:${APK_VERSION}" \
|
||||||
|
--info "description:The universal proxy platform." \
|
||||||
|
--info "arch:${ARCHITECTURE}" \
|
||||||
|
--info "license:GPL-3.0-or-later" \
|
||||||
|
--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 "provider-priority:100" \
|
||||||
|
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
||||||
|
--files "$ROOT_DIR" \
|
||||||
|
--output "$OUTPUT_PATH"
|
||||||
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@@ -6,7 +6,7 @@
|
|||||||
":disableRateLimiting"
|
":disableRateLimiting"
|
||||||
],
|
],
|
||||||
"baseBranches": [
|
"baseBranches": [
|
||||||
"dev-next"
|
"unstable"
|
||||||
],
|
],
|
||||||
"golang": {
|
"golang": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
|
|||||||
45
.github/setup_go_for_macos1013.sh
vendored
Executable file
45
.github/setup_go_for_macos1013.sh
vendored
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION="1.25.8"
|
||||||
|
PATCH_COMMITS=(
|
||||||
|
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
||||||
|
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
||||||
|
)
|
||||||
|
CURL_ARGS=(
|
||||||
|
-fL
|
||||||
|
--silent
|
||||||
|
--show-error
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||||
|
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/go"
|
||||||
|
cd "$HOME/go"
|
||||||
|
wget "https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz"
|
||||||
|
tar -xzf "go${VERSION}.darwin-arm64.tar.gz"
|
||||||
|
#cp -a go go_bootstrap
|
||||||
|
mv go go_osx
|
||||||
|
cd go_osx
|
||||||
|
|
||||||
|
# these patch URLs only work on golang1.25.x
|
||||||
|
# that means after golang1.26 release it must be changed
|
||||||
|
# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/
|
||||||
|
# revert:
|
||||||
|
# 33d3f603c1: "cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking"
|
||||||
|
# 937368f84e: "crypto/x509: change how we retrieve chains on darwin"
|
||||||
|
|
||||||
|
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
||||||
|
curl "${CURL_ARGS[@]}" "https://github.com/SagerNet/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external
|
||||||
|
# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the
|
||||||
|
# stdlib (crypto/x509) is compiled from patched src automatically.
|
||||||
|
#cd src
|
||||||
|
#GOROOT_BOOTSTRAP="$HOME/go/go_bootstrap" ./make.bash
|
||||||
|
#cd ../..
|
||||||
|
#rm -rf go_bootstrap "go${VERSION}.darwin-arm64.tar.gz"
|
||||||
39
.github/setup_go_for_windows7.sh
vendored
39
.github/setup_go_for_windows7.sh
vendored
@@ -1,16 +1,35 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.25.4"
|
set -euo pipefail
|
||||||
|
|
||||||
mkdir -p $HOME/go
|
VERSION="1.25.8"
|
||||||
cd $HOME/go
|
PATCH_COMMITS=(
|
||||||
|
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
||||||
|
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
||||||
|
"a90777dcf692dd2168577853ba743b4338721b06"
|
||||||
|
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
|
||||||
|
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
|
||||||
|
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
|
||||||
|
)
|
||||||
|
CURL_ARGS=(
|
||||||
|
-fL
|
||||||
|
--silent
|
||||||
|
--show-error
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||||
|
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/go"
|
||||||
|
cd "$HOME/go"
|
||||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
mv go go_win7
|
mv go go_win7
|
||||||
cd go_win7
|
cd go_win7
|
||||||
|
|
||||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
# this patch file only works on golang1.25.x
|
# these patch URLs only work on golang1.25.x
|
||||||
# that means after golang1.26 release it must be changed
|
# that means after golang1.26 release it must be changed
|
||||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||||
# revert:
|
# revert:
|
||||||
@@ -18,10 +37,10 @@ cd go_win7
|
|||||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
# fixes:
|
||||||
|
# bed309eff415bcb3c77dd4bc3277b682b89a388d: "Fix os.RemoveAll not working on Windows7"
|
||||||
|
# 34b899c2fb39b092db4fa67c4417e41dc046be4b: "Revert \"os: remove 5ms sleep on Windows in (*Process).Wait\""
|
||||||
|
|
||||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
||||||
|
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
||||||
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
done
|
||||||
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
|
||||||
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
|
||||||
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
|
||||||
|
|||||||
11
.github/setup_musl_cross.sh
vendored
11
.github/setup_musl_cross.sh
vendored
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -xeuo pipefail
|
|
||||||
|
|
||||||
TARGET="$1"
|
|
||||||
|
|
||||||
# Download musl-cross toolchain from musl.cc
|
|
||||||
cd "$HOME"
|
|
||||||
wget -q "https://musl.cc/${TARGET}-cross.tgz"
|
|
||||||
mkdir -p musl-cross
|
|
||||||
tar -xf "${TARGET}-cross.tgz" -C musl-cross --strip-components=1
|
|
||||||
rm "${TARGET}-cross.tgz"
|
|
||||||
10
.github/update_cronet.sh
vendored
10
.github/update_cronet.sh
vendored
@@ -1,7 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
PROJECTS=$(dirname "$0")/../..
|
set -e -o pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
|
PROJECTS=$SCRIPT_DIR/../..
|
||||||
|
|
||||||
|
git -C $PROJECTS/cronet-go fetch origin main
|
||||||
git -C $PROJECTS/cronet-go fetch origin go
|
git -C $PROJECTS/cronet-go fetch origin go
|
||||||
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
||||||
|
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
||||||
|
|||||||
13
.github/update_cronet_dev.sh
vendored
Executable file
13
.github/update_cronet_dev.sh
vendored
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(dirname "$0")
|
||||||
|
PROJECTS=$SCRIPT_DIR/../..
|
||||||
|
|
||||||
|
git -C $PROJECTS/cronet-go fetch origin dev
|
||||||
|
git -C $PROJECTS/cronet-go fetch origin go_dev
|
||||||
|
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)
|
||||||
|
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go_dev)
|
||||||
|
go mod tidy
|
||||||
|
git -C $PROJECTS/cronet-go rev-parse origin/dev > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
||||||
459
.github/workflows/build.yml
vendored
459
.github/workflows/build.yml
vendored
@@ -25,8 +25,9 @@ on:
|
|||||||
- publish-android
|
- publish-android
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main-next
|
- stable
|
||||||
- dev-next
|
- testing
|
||||||
|
- unstable
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||||
@@ -46,7 +47,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -69,49 +70,61 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
- { os: linux, arch: amd64, variant: purego, naive: true }
|
||||||
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
||||||
|
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: "x86_64" }
|
||||||
|
|
||||||
|
- { os: linux, arch: arm64, variant: purego, naive: true }
|
||||||
|
- { os: linux, arch: arm64, variant: glibc, naive: true }
|
||||||
|
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, alpine: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
|
|
||||||
|
- { os: linux, arch: "386", go386: sse2 }
|
||||||
|
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
|
||||||
|
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: "i386_pentium4" }
|
||||||
|
|
||||||
|
- { os: linux, arch: arm, goarm: "7" }
|
||||||
|
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
|
||||||
|
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, openwrt: "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" }
|
||||||
|
|
||||||
|
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
|
||||||
|
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
|
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: riscv64, naive: true, variant: glibc }
|
||||||
|
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
|
||||||
|
- { os: linux, arch: loong64, naive: true, variant: glibc }
|
||||||
|
- { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
|
||||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "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" }
|
|
||||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
- { os: linux, arch: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" }
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
- { os: linux, arch: mipsle, gomips: softfloat }
|
||||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
- { os: linux, arch: mips64le, gomips: hardfloat }
|
||||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
- { os: linux, arch: riscv64 }
|
||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
- { os: linux, arch: loong64 }
|
||||||
|
|
||||||
- { os: windows, arch: amd64 }
|
|
||||||
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
||||||
- { os: windows, arch: "386" }
|
|
||||||
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
||||||
- { os: windows, arch: arm64 }
|
|
||||||
|
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android23" }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" }
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android23" }
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
- { os: android, arch: "386", ndk: "i686-linux-android23" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
|
if: ${{ ! matrix.legacy_win7 }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Setup Go 1.24
|
|
||||||
if: matrix.legacy_go124
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.24.10
|
|
||||||
- name: Cache Go for Windows 7
|
- name: Cache Go for Windows 7
|
||||||
if: matrix.legacy_win7
|
if: matrix.legacy_win7
|
||||||
id: cache-go-for-windows7
|
id: cache-go-for-windows7
|
||||||
@@ -119,9 +132,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_win7
|
~/go/go_win7
|
||||||
key: go_win7_1254
|
key: go_win7_1258
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Go for Windows 7
|
||||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_go_for_windows7.sh
|
.github/setup_go_for_windows7.sh
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Go for Windows 7
|
||||||
@@ -135,6 +150,54 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28
|
||||||
local-cache: true
|
local-cache: true
|
||||||
|
- name: Clone cronet-go
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
||||||
|
git init ~/cronet-go
|
||||||
|
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||||||
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
|
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||||||
|
- name: Regenerate Debian keyring
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
||||||
|
cd ~/cronet-go
|
||||||
|
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
||||||
|
- name: Cache Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
id: cache-chromium-toolchain
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
||||||
|
~/cronet-go/naiveproxy/src/gn/out/
|
||||||
|
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
||||||
|
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
||||||
|
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
|
- name: Download Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||||||
|
else
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} download-toolchain
|
||||||
|
fi
|
||||||
|
- name: Set Chromium toolchain environment
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} env >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
@@ -142,18 +205,83 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
if [[ "${{ matrix.os }}" == "android" ]]; then
|
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
||||||
TAGS="${TAGS},with_naive_outbound"
|
else
|
||||||
|
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
||||||
|
fi
|
||||||
|
if [[ "${{ matrix.variant }}" == "purego" ]]; then
|
||||||
|
TAGS="${TAGS},with_purego"
|
||||||
|
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
TAGS="${TAGS},with_musl"
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Set shared ldflags
|
||||||
if: matrix.os != 'android'
|
run: |
|
||||||
|
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build (purego)
|
||||||
|
if: matrix.variant == 'purego'
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: ${{ matrix.os }}
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract libcronet.so
|
||||||
|
if: matrix.variant == 'purego' && matrix.naive
|
||||||
|
run: |
|
||||||
|
cd ~/cronet-go
|
||||||
|
CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist
|
||||||
|
- name: Build (glibc)
|
||||||
|
if: matrix.variant == 'glibc'
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build (musl)
|
||||||
|
if: matrix.variant == 'musl'
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GO386: ${{ matrix.go386 }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build (non-variant)
|
||||||
|
if: matrix.os != 'android' && matrix.variant == ''
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -173,7 +301,7 @@ jobs:
|
|||||||
export CXX="${CC}++"
|
export CXX="${CC}++"
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@@ -192,6 +320,11 @@ jobs:
|
|||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
||||||
fi
|
fi
|
||||||
|
if [[ "${{ matrix.variant }}" == "glibc" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-glibc"
|
||||||
|
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
DIR_NAME="${DIR_NAME}-musl"
|
||||||
|
fi
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||||
@@ -242,7 +375,7 @@ jobs:
|
|||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
cp .fpm_systemd .fpm
|
cp .fpm_pacman .fpm
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
@@ -263,6 +396,30 @@ jobs:
|
|||||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
||||||
done
|
done
|
||||||
rm "dist/openwrt.deb"
|
rm "dist/openwrt.deb"
|
||||||
|
- name: Install apk-tools
|
||||||
|
if: matrix.openwrt != '' || matrix.alpine != ''
|
||||||
|
run: |-
|
||||||
|
docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c "apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk"
|
||||||
|
- name: Package OpenWrt APK
|
||||||
|
if: matrix.openwrt != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
for architecture in ${{ matrix.openwrt }}; do
|
||||||
|
.github/build_openwrt_apk.sh \
|
||||||
|
"$architecture" \
|
||||||
|
"${{ needs.calculate_version.outputs.version }}" \
|
||||||
|
"dist/sing-box" \
|
||||||
|
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk"
|
||||||
|
done
|
||||||
|
- name: Package Alpine APK
|
||||||
|
if: matrix.alpine != ''
|
||||||
|
run: |-
|
||||||
|
set -xeuo pipefail
|
||||||
|
.github/build_alpine_apk.sh \
|
||||||
|
"${{ matrix.alpine }}" \
|
||||||
|
"${{ needs.calculate_version.outputs.version }}" \
|
||||||
|
"dist/sing-box" \
|
||||||
|
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk"
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
@@ -274,15 +431,18 @@ jobs:
|
|||||||
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
||||||
else
|
else
|
||||||
cp sing-box "${DIR_NAME}"
|
cp sing-box "${DIR_NAME}"
|
||||||
|
if [ -f libcronet.so ]; then
|
||||||
|
cp libcronet.so "${DIR_NAME}"
|
||||||
|
fi
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
||||||
fi
|
fi
|
||||||
rm -r "${DIR_NAME}"
|
rm -r "${DIR_NAME}"
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: rm dist/sing-box
|
run: rm -f dist/sing-box dist/libcronet.so
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_darwin:
|
build_darwin:
|
||||||
name: Build Darwin binaries
|
name: Build Darwin binaries
|
||||||
@@ -295,22 +455,36 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- { arch: amd64 }
|
- { arch: amd64 }
|
||||||
- { arch: arm64 }
|
- { arch: arm64 }
|
||||||
- { arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
|
- { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! matrix.legacy_go124 }}
|
if: ${{ ! matrix.legacy_osx }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.3
|
go-version: ^1.25.3
|
||||||
- name: Setup Go 1.24
|
- name: Cache Go for macOS 10.13
|
||||||
if: matrix.legacy_go124
|
if: matrix.legacy_osx
|
||||||
uses: actions/setup-go@v5
|
id: cache-go-for-macos1013
|
||||||
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
go-version: ~1.24.6
|
path: |
|
||||||
|
~/go/go_osx
|
||||||
|
key: go_osx_1258
|
||||||
|
- name: Setup Go for macOS 10.13
|
||||||
|
if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
run: |-
|
||||||
|
.github/setup_go_for_macos1013.sh
|
||||||
|
- name: Setup Go for macOS 10.13
|
||||||
|
if: matrix.legacy_osx
|
||||||
|
run: |-
|
||||||
|
echo "PATH=$HOME/go/go_osx/bin:$PATH" >> $GITHUB_ENV
|
||||||
|
echo "GOROOT=$HOME/go/go_osx" >> $GITHUB_ENV
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
@@ -318,19 +492,27 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_naive_outbound,badlinkname,tfogo_checklinkname0'
|
if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then
|
||||||
|
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
||||||
|
else
|
||||||
|
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
||||||
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Set shared ldflags
|
||||||
|
run: |
|
||||||
|
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
GOOS: darwin
|
GOOS: darwin
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
- name: Set name
|
||||||
run: |-
|
run: |-
|
||||||
@@ -355,25 +537,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_naive_linux:
|
build_windows:
|
||||||
name: Build Linux with naive outbound
|
name: Build Windows binaries
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
runs-on: ubuntu-latest
|
runs-on: windows-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Linux glibc (dynamic linking with Debian Bullseye sysroot)
|
- { arch: amd64, naive: true }
|
||||||
- { arch: amd64, sysroot_arch: amd64, sysroot_sha: "36a164623d03f525e3dfb783a5e9b8a00e98e1ddd2b5cff4e449bd016dd27e50", cc_target: "x86_64-linux-gnu", suffix: "-naive" }
|
- { arch: "386" }
|
||||||
- { arch: arm64, sysroot_arch: arm64, sysroot_sha: "2f915d821eec27515c0c6d21b69898e23762908d8d7ccc1aa2a8f5f25e8b7e18", cc_target: "aarch64-linux-gnu", suffix: "-naive" }
|
- { arch: arm64, naive: true }
|
||||||
- { arch: "386", sysroot_arch: i386, sysroot_sha: "63f0e5128b84f7b0421956a4a40affa472be8da0e58caf27e9acbc84072daee7", cc_target: "i686-linux-gnu", suffix: "-naive" }
|
|
||||||
- { arch: arm, goarm: "7", sysroot_arch: armhf, sysroot_sha: "47b3a0b161ca011b2b33d4fc1ef6ef269b8208a0b7e4c900700c345acdfd1814", cc_target: "arm-linux-gnueabihf", suffix: "-naive" }
|
|
||||||
# Linux musl (static linking)
|
|
||||||
- { arch: amd64, musl: true, cc_target: "x86_64-linux-musl", suffix: "-naive-musl" }
|
|
||||||
- { arch: arm64, musl: true, cc_target: "aarch64-linux-musl", suffix: "-naive-musl" }
|
|
||||||
- { arch: "386", musl: true, cc_target: "i686-linux-musl", suffix: "-naive-musl" }
|
|
||||||
- { arch: arm, goarm: "7", musl: true, cc_target: "arm-linux-musleabihf", suffix: "-naive-musl" }
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
@@ -385,101 +560,75 @@ jobs:
|
|||||||
go-version: ^1.25.4
|
go-version: ^1.25.4
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$env:GITHUB_ENV"
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
- name: Download sysroot (glibc)
|
- name: Build
|
||||||
if: ${{ ! matrix.musl }}
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS
|
||||||
wget -q "https://commondatastorage.googleapis.com/chrome-linux-sysroot/${{ matrix.sysroot_sha }}" -O sysroot.tar.xz
|
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
||||||
mkdir -p /tmp/sysroot
|
|
||||||
tar -xf sysroot.tar.xz -C /tmp/sysroot
|
|
||||||
- name: Install cross compiler (glibc)
|
|
||||||
if: ${{ ! matrix.musl }}
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y clang lld
|
|
||||||
if [[ "${{ matrix.arch }}" == "arm64" ]]; then
|
|
||||||
sudo apt-get install -y libc6-dev-arm64-cross
|
|
||||||
elif [[ "${{ matrix.arch }}" == "386" ]]; then
|
|
||||||
sudo apt-get install -y libc6-dev-i386-cross
|
|
||||||
elif [[ "${{ matrix.arch }}" == "arm" ]]; then
|
|
||||||
sudo apt-get install -y libc6-dev-armhf-cross
|
|
||||||
fi
|
|
||||||
- name: Install musl cross compiler
|
|
||||||
if: matrix.musl
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
.github/setup_musl_cross.sh "${{ matrix.cc_target }}"
|
|
||||||
echo "PATH=$HOME/musl-cross/bin:$PATH" >> $GITHUB_ENV
|
|
||||||
- name: Set build tags
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_naive_outbound,badlinkname,tfogo_checklinkname0'
|
|
||||||
if [[ "${{ matrix.musl }}" == "true" ]]; then
|
|
||||||
TAGS="${TAGS},with_musl"
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (glibc)
|
|
||||||
if: ${{ ! matrix.musl }}
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0 -linkmode=external -extldflags "-fuse-ld=lld --sysroot=/tmp/sysroot"' \
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "0"
|
||||||
GOOS: linux
|
GOOS: windows
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
CC: "clang --target=${{ matrix.cc_target }} --sysroot=/tmp/sysroot"
|
|
||||||
CXX: "clang++ --target=${{ matrix.cc_target }} --sysroot=/tmp/sysroot"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build (musl)
|
- name: Build
|
||||||
if: matrix.musl
|
if: ${{ !matrix.naive }}
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS
|
||||||
|
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0 -linkmode=external -extldflags "-static"' \
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "0"
|
||||||
GOOS: linux
|
GOOS: windows
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
CC: "${{ matrix.cc_target }}-gcc"
|
|
||||||
CXX: "${{ matrix.cc_target }}-g++"
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
- name: Extract libcronet.dll
|
||||||
run: |-
|
if: matrix.naive
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-linux-${{ matrix.arch }}"
|
run: |
|
||||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
$CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION
|
||||||
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
$env:CGO_ENABLED = "0"
|
||||||
fi
|
go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist
|
||||||
DIR_NAME="${DIR_NAME}${{ matrix.suffix }}"
|
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Archive
|
- name: Archive
|
||||||
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
|
||||||
cd dist
|
mkdir "dist/$DIR_NAME"
|
||||||
mkdir -p "${DIR_NAME}"
|
Copy-Item LICENSE "dist/$DIR_NAME"
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
|
||||||
cp sing-box "${DIR_NAME}"
|
Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME"
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
||||||
rm -r "${DIR_NAME}"
|
Remove-Item -Recurse "dist/$DIR_NAME"
|
||||||
|
- name: Archive
|
||||||
|
if: ${{ !matrix.naive }}
|
||||||
|
run: |
|
||||||
|
$DIR_NAME = "sing-box-${{ needs.calculate_version.outputs.version }}-windows-${{ matrix.arch }}"
|
||||||
|
mkdir "dist/$DIR_NAME"
|
||||||
|
Copy-Item LICENSE "dist/$DIR_NAME"
|
||||||
|
Copy-Item "dist/sing-box.exe" "dist/$DIR_NAME"
|
||||||
|
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
||||||
|
Remove-Item -Recurse "dist/$DIR_NAME"
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: rm dist/sing-box
|
if: matrix.naive
|
||||||
|
run: Remove-Item dist/sing-box.exe, dist/libcronet.dll
|
||||||
|
- name: Cleanup
|
||||||
|
if: ${{ !matrix.naive }}
|
||||||
|
run: Remove-Item dist/sing-box.exe
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-linux_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.suffix }}
|
name: binary-windows_${{ matrix.arch }}
|
||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android') && github.ref != 'refs/heads/oldstable'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
@@ -492,7 +641,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -515,12 +664,12 @@ jobs:
|
|||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: github.ref == 'refs/heads/dev-next'
|
if: github.ref == 'refs/heads/testing'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -540,9 +689,9 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |-
|
run: |-
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp libbox.aar clients/android/app/libs
|
cp *.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
@@ -550,8 +699,18 @@ jobs:
|
|||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
#cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
||||||
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist
|
cp clients/android/app/build/outputs/apk/other/release/*.apk dist
|
||||||
|
cp clients/android/app/build/outputs/apk/otherLegacy/release/*.apk dist
|
||||||
|
VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2)
|
||||||
|
VERSION_NAME=$(grep VERSION_NAME clients/android/version.properties | cut -d= -f2)
|
||||||
|
cat > dist/SFA-version-metadata.json << EOF
|
||||||
|
{
|
||||||
|
"version_code": ${VERSION_CODE},
|
||||||
|
"version_name": "${VERSION_NAME}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
cat dist/SFA-version-metadata.json
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -559,7 +718,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
publish_android:
|
publish_android:
|
||||||
name: Publish Android
|
name: Publish Android
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' && github.ref != 'refs/heads/oldstable'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
@@ -572,7 +731,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -595,12 +754,12 @@ jobs:
|
|||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
if: github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: github.ref == 'refs/heads/dev-next'
|
if: github.ref == 'refs/heads/testing'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -613,7 +772,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/update_android_version --ci
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp libbox.aar clients/android/app/libs
|
cp *.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
echo -n "$SERVICE_ACCOUNT_CREDENTIALS" | base64 --decode > service-account-credentials.json
|
||||||
./gradlew :app:publishPlayReleaseBundle
|
./gradlew :app:publishPlayReleaseBundle
|
||||||
@@ -625,7 +784,7 @@ jobs:
|
|||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-26
|
runs-on: macos-26
|
||||||
if: false
|
if: false # github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Apple' || inputs.build == 'app-store' || inputs.build == 'iOS' || inputs.build == 'macOS' || inputs.build == 'tvOS' || inputs.build == 'macOS-standalone'
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
strategy:
|
strategy:
|
||||||
@@ -671,7 +830,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -679,12 +838,12 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
if: matrix.if && github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
if: matrix.if && github.ref == 'refs/heads/testing'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -770,7 +929,7 @@ jobs:
|
|||||||
-authenticationKeyID $ASC_KEY_ID \
|
-authenticationKeyID $ASC_KEY_ID \
|
||||||
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
|
||||||
- name: Publish to TestFlight
|
- name: Publish to TestFlight
|
||||||
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/testing'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
|
||||||
- name: Build image
|
- name: Build image
|
||||||
@@ -790,7 +949,7 @@ jobs:
|
|||||||
--app-drop-link 0 0 \
|
--app-drop-link 0 0 \
|
||||||
--skip-jenkins \
|
--skip-jenkins \
|
||||||
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
SFM.dmg "${{ matrix.export_path }}/SFM.app"
|
||||||
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
xcrun notarytool submit "SFM.dmg" --wait --keychain-profile "notarytool-password"
|
||||||
cd "${{ matrix.archive }}"
|
cd "${{ matrix.archive }}"
|
||||||
zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
popd
|
popd
|
||||||
@@ -812,7 +971,7 @@ jobs:
|
|||||||
- calculate_version
|
- calculate_version
|
||||||
- build
|
- build
|
||||||
- build_darwin
|
- build_darwin
|
||||||
- build_naive_linux
|
- build_windows
|
||||||
- build_android
|
- build_android
|
||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
187
.github/workflows/docker.yml
vendored
187
.github/workflows/docker.yml
vendored
@@ -1,6 +1,10 @@
|
|||||||
name: Publish Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
#push:
|
||||||
|
# branches:
|
||||||
|
# - stable
|
||||||
|
# - testing
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -13,20 +17,165 @@ env:
|
|||||||
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build_binary:
|
||||||
|
name: Build binary
|
||||||
|
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
include:
|
||||||
- linux/amd64
|
# Naive-enabled builds (musl)
|
||||||
- linux/arm/v6
|
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
|
||||||
- linux/arm/v7
|
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
||||||
- linux/arm64
|
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
||||||
- linux/386
|
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
||||||
- linux/ppc64le
|
- { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" }
|
||||||
- linux/riscv64
|
- { arch: riscv64, naive: true, docker_platform: "linux/riscv64" }
|
||||||
- linux/s390x
|
- { arch: loong64, naive: true, docker_platform: "linux/loong64" }
|
||||||
|
# Non-naive builds
|
||||||
|
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
|
||||||
|
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
|
||||||
|
- { arch: s390x, docker_platform: "linux/s390x" }
|
||||||
|
steps:
|
||||||
|
- name: Get commit to build
|
||||||
|
id: ref
|
||||||
|
run: |-
|
||||||
|
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||||
|
ref="${{ github.ref_name }}"
|
||||||
|
else
|
||||||
|
ref="${{ github.event.inputs.tag }}"
|
||||||
|
fi
|
||||||
|
echo "ref=$ref"
|
||||||
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ~1.25.8
|
||||||
|
- name: Clone cronet-go
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
||||||
|
git init ~/cronet-go
|
||||||
|
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||||||
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
|
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||||||
|
- name: Regenerate Debian keyring
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
||||||
|
cd ~/cronet-go
|
||||||
|
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
||||||
|
- name: Cache Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
id: cache-chromium-toolchain
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
||||||
|
~/cronet-go/naiveproxy/src/gn/out/
|
||||||
|
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
||||||
|
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
||||||
|
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
|
- name: Download Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
VERSION=$(go run ./cmd/internal/read_tag)
|
||||||
|
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Set Chromium toolchain environment
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
||||||
|
- name: Set build tags
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
|
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
||||||
|
else
|
||||||
|
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
||||||
|
fi
|
||||||
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
|
- name: Set shared ldflags
|
||||||
|
run: |
|
||||||
|
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build (naive)
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
- name: Build (non-naive)
|
||||||
|
if: ${{ ! matrix.naive }}
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "0"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
- name: Prepare artifact
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.docker_platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
# Rename binary to include arch info for Dockerfile.binary
|
||||||
|
BINARY_NAME="sing-box-${{ matrix.arch }}"
|
||||||
|
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||||
|
BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}"
|
||||||
|
fi
|
||||||
|
mv sing-box "${BINARY_NAME}"
|
||||||
|
echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV
|
||||||
|
- name: Upload binary
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ env.BINARY_NAME }}
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 1
|
||||||
|
build_docker:
|
||||||
|
name: Build Docker image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- build_binary
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { platform: "linux/amd64" }
|
||||||
|
- { platform: "linux/arm/v6" }
|
||||||
|
- { platform: "linux/arm/v7" }
|
||||||
|
- { platform: "linux/arm64" }
|
||||||
|
- { platform: "linux/386" }
|
||||||
|
# mipsle: no base Docker image available for this platform
|
||||||
|
- { platform: "linux/ppc64le" }
|
||||||
|
- { platform: "linux/riscv64" }
|
||||||
|
- { platform: "linux/s390x" }
|
||||||
|
- { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" }
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -47,6 +196,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
platform=${{ matrix.platform }}
|
platform=${{ matrix.platform }}
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
- name: Download binary
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
name: binary-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: .
|
||||||
|
- name: Prepare binary
|
||||||
|
run: |
|
||||||
|
# Find and make the binary executable
|
||||||
|
chmod +x sing-box-*
|
||||||
|
ls -la sing-box-*
|
||||||
- name: Setup QEMU
|
- name: Setup QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
@@ -68,8 +227,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
context: .
|
context: .
|
||||||
|
file: Dockerfile.binary
|
||||||
build-args: |
|
build-args: |
|
||||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
@@ -85,9 +245,10 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
merge:
|
merge:
|
||||||
|
if: github.event_name != 'push'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- build
|
- build_docker
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -121,6 +282,7 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
|
if: github.event_name != 'push'
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
@@ -128,6 +290,7 @@ jobs:
|
|||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||||
- name: Inspect image
|
- name: Inspect image
|
||||||
|
if: github.event_name != 'push'
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||||
|
|||||||
16
.github/workflows/lint.yml
vendored
16
.github/workflows/lint.yml
vendored
@@ -3,18 +3,20 @@ name: Lint
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- stable-next
|
- oldstable
|
||||||
- main-next
|
- stable
|
||||||
- dev-next
|
- testing
|
||||||
|
- unstable
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '!.github/workflows/lint.yml'
|
- '!.github/workflows/lint.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- stable-next
|
- oldstable
|
||||||
- main-next
|
- stable
|
||||||
- dev-next
|
- testing
|
||||||
|
- unstable
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -32,7 +34,7 @@ jobs:
|
|||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
version: v2.4.0
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
verify: false
|
verify: false
|
||||||
|
|||||||
100
.github/workflows/linux.yml
vendored
100
.github/workflows/linux.yml
vendored
@@ -1,6 +1,10 @@
|
|||||||
name: Build Linux Packages
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
#push:
|
||||||
|
# branches:
|
||||||
|
# - stable
|
||||||
|
# - testing
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
@@ -19,6 +23,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
calculate_version:
|
calculate_version:
|
||||||
name: Calculate version
|
name: Calculate version
|
||||||
|
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
@@ -30,7 +35,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -52,17 +57,19 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
# Naive-enabled builds (musl)
|
||||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
|
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||||
|
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
|
||||||
|
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||||
|
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel }
|
||||||
|
- { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 }
|
||||||
|
- { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 }
|
||||||
|
# Non-naive builds (unsupported architectures)
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
|
||||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
|
||||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
|
||||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
@@ -71,13 +78,47 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ~1.25.8
|
||||||
- name: Setup Android NDK
|
- name: Clone cronet-go
|
||||||
if: matrix.os == 'android'
|
if: matrix.naive
|
||||||
uses: nttld/setup-ndk@v1
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
||||||
|
git init ~/cronet-go
|
||||||
|
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||||||
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
|
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||||||
|
- name: Regenerate Debian keyring
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
||||||
|
cd ~/cronet-go
|
||||||
|
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
||||||
|
- name: Cache Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
id: cache-chromium-toolchain
|
||||||
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
path: |
|
||||||
local-cache: true
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
||||||
|
~/cronet-go/naiveproxy/src/gn/out/
|
||||||
|
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
||||||
|
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
||||||
|
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
|
- name: Download Chromium toolchain
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||||||
|
- name: Set Chromium toolchain environment
|
||||||
|
if: matrix.naive
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
cd ~/cronet-go
|
||||||
|
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
@@ -85,14 +126,38 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0'
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
|
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
||||||
|
else
|
||||||
|
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
||||||
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Set shared ldflags
|
||||||
|
run: |
|
||||||
|
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
||||||
|
- name: Build (naive)
|
||||||
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: "1"
|
||||||
|
GOOS: linux
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
GOARM: ${{ matrix.goarm }}
|
||||||
|
GOMIPS: ${{ matrix.gomips }}
|
||||||
|
GOMIPS64: ${{ matrix.gomips }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build (non-naive)
|
||||||
|
if: ${{ ! matrix.naive }}
|
||||||
|
run: |
|
||||||
|
set -xeuo pipefail
|
||||||
|
mkdir -p dist
|
||||||
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
|
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -185,5 +250,6 @@ jobs:
|
|||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
- name: Publish packages
|
- name: Publish packages
|
||||||
|
if: github.event_name != 'push'
|
||||||
run: |-
|
run: |-
|
||||||
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,6 +12,9 @@
|
|||||||
/*.jar
|
/*.jar
|
||||||
/*.aar
|
/*.aar
|
||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
|
/experimental/libbox/*.aar
|
||||||
|
/experimental/libbox/*.xcframework/
|
||||||
|
/experimental/libbox/*.nupkg
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
/venv/
|
/venv/
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ run:
|
|||||||
- with_utls
|
- with_utls
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
|
- with_ccm
|
||||||
|
- with_ocm
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
linters:
|
linters:
|
||||||
default: none
|
default: none
|
||||||
enable:
|
enable:
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags \
|
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" \
|
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
||||||
|
&& go build -v -trimpath -tags "$TAGS" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" $LDFLAGS_SHARED -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
|||||||
14
Dockerfile.binary
Normal file
14
Dockerfile.binary
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
ARG BASE_IMAGE=alpine
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETVARIANT
|
||||||
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
RUN set -ex \
|
||||||
|
&& if command -v apk > /dev/null; then \
|
||||||
|
apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \
|
||||||
|
else \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*; \
|
||||||
|
fi
|
||||||
|
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
|
||||||
|
ENTRYPOINT ["sing-box"]
|
||||||
120
Makefile
120
Makefile
@@ -1,15 +1,18 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0
|
TAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
LDFLAGS_SHARED = $(shell cat release/LDFLAGS)
|
||||||
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
SING_FFI ?= sing-ffi
|
||||||
|
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
@@ -37,8 +40,11 @@ fmt:
|
|||||||
@gofmt -s -w .
|
@gofmt -s -w .
|
||||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||||
|
|
||||||
|
fmt_docs:
|
||||||
|
go run ./cmd/internal/format_docs
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@v0.8.0
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
go install -v github.com/daixiang0/gci@latest
|
go install -v github.com/daixiang0/gci@latest
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@@ -49,7 +55,7 @@ lint:
|
|||||||
GOOS=freebsd golangci-lint run ./...
|
GOOS=freebsd golangci-lint run ./...
|
||||||
|
|
||||||
lint_install:
|
lint_install:
|
||||||
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
|
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@@ -86,12 +92,12 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./gradlew --stop
|
||||||
|
|
||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/play/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
@@ -106,7 +112,7 @@ build_ios:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild clean -scheme SFI && \
|
xcodebuild clean -scheme SFI && \
|
||||||
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
||||||
|
|
||||||
upload_ios_app_store:
|
upload_ios_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -127,7 +133,7 @@ release_ios: build_ios upload_ios_app_store
|
|||||||
build_macos:
|
build_macos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFM.xcarchive && \
|
rm -rf build/SFM.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates
|
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
||||||
|
|
||||||
upload_macos_app_store:
|
upload_macos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -136,54 +142,50 @@ upload_macos_app_store:
|
|||||||
release_macos: build_macos upload_macos_app_store
|
release_macos: build_macos upload_macos_app_store
|
||||||
|
|
||||||
build_macos_standalone:
|
build_macos_standalone:
|
||||||
cd ../sing-box-for-apple && \
|
$(MAKE) -C ../sing-box-for-apple archive_macos_standalone
|
||||||
rm -rf build/SFM.System.xcarchive && \
|
|
||||||
xcodebuild archive -scheme SFM.System -configuration Release -archivePath build/SFM.System.xcarchive -allowProvisioningUpdates
|
|
||||||
|
|
||||||
build_macos_dmg:
|
build_macos_dmg:
|
||||||
rm -rf dist/SFM
|
$(MAKE) -C ../sing-box-for-apple build_macos_dmg
|
||||||
mkdir -p dist/SFM
|
|
||||||
cd ../sing-box-for-apple && \
|
build_macos_pkg:
|
||||||
rm -rf build/SFM.System && \
|
$(MAKE) -C ../sing-box-for-apple build_macos_pkg
|
||||||
rm -rf build/SFM.dmg && \
|
|
||||||
xcodebuild -exportArchive \
|
|
||||||
-archivePath "build/SFM.System.xcarchive" \
|
|
||||||
-exportOptionsPlist SFM.System/Export.plist -allowProvisioningUpdates \
|
|
||||||
-exportPath "build/SFM.System" && \
|
|
||||||
create-dmg \
|
|
||||||
--volname "sing-box" \
|
|
||||||
--volicon "build/SFM.System/SFM.app/Contents/Resources/AppIcon.icns" \
|
|
||||||
--icon "SFM.app" 0 0 \
|
|
||||||
--hide-extension "SFM.app" \
|
|
||||||
--app-drop-link 0 0 \
|
|
||||||
--skip-jenkins \
|
|
||||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
|
||||||
|
|
||||||
notarize_macos_dmg:
|
notarize_macos_dmg:
|
||||||
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
$(MAKE) -C ../sing-box-for-apple notarize_macos_dmg
|
||||||
--keychain-profile "notarytool-password" \
|
|
||||||
--no-s3-acceleration
|
notarize_macos_pkg:
|
||||||
|
$(MAKE) -C ../sing-box-for-apple notarize_macos_pkg
|
||||||
|
|
||||||
upload_macos_dmg:
|
upload_macos_dmg:
|
||||||
cd dist/SFM && \
|
mkdir -p dist/SFM
|
||||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
cp ../sing-box-for-apple/build/SFM-Apple.dmg "dist/SFM/SFM-${VERSION}-Apple.dmg"
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
cp ../sing-box-for-apple/build/SFM-Intel.dmg "dist/SFM/SFM-${VERSION}-Intel.dmg"
|
||||||
|
cp ../sing-box-for-apple/build/SFM-Universal.dmg "dist/SFM/SFM-${VERSION}-Universal.dmg"
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.dmg"
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.dmg"
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.dmg"
|
||||||
|
|
||||||
|
upload_macos_pkg:
|
||||||
|
mkdir -p dist/SFM
|
||||||
|
cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg"
|
||||||
|
cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg"
|
||||||
|
cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg"
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
|
||||||
|
|
||||||
upload_macos_dsyms:
|
upload_macos_dsyms:
|
||||||
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
mkdir -p dist/SFM
|
||||||
zip -r SFM.dSYMs.zip dSYMs && \
|
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
|
||||||
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
||||||
popd && \
|
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
||||||
cd dist/SFM && \
|
|
||||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
|
||||||
|
|
||||||
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFT.xcarchive && \
|
rm -rf build/SFT.xcarchive && \
|
||||||
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
||||||
|
|
||||||
upload_tvos_app_store:
|
upload_tvos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -207,12 +209,12 @@ update_apple_version:
|
|||||||
update_macos_version:
|
update_macos_version:
|
||||||
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
|
||||||
|
|
||||||
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
release_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
||||||
|
|
||||||
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
release_apple_beta: update_apple_version release_ios release_macos release_tvos
|
||||||
|
|
||||||
publish_testflight:
|
publish_testflight:
|
||||||
go run -v ./cmd/internal/app_store_connect publish_testflight
|
go run -v ./cmd/internal/app_store_connect publish_testflight $(filter-out $@,$(MAKECMDGOALS))
|
||||||
|
|
||||||
prepare_app_store:
|
prepare_app_store:
|
||||||
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
go run -v ./cmd/internal/app_store_connect prepare_app_store
|
||||||
@@ -235,22 +237,21 @@ test_stdio:
|
|||||||
lib_android:
|
lib_android:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox -target android
|
||||||
|
|
||||||
lib_android_debug:
|
|
||||||
go run ./cmd/internal/build_libbox -target android -debug
|
|
||||||
|
|
||||||
lib_apple:
|
lib_apple:
|
||||||
go run ./cmd/internal/build_libbox -target apple
|
go run ./cmd/internal/build_libbox -target apple
|
||||||
|
|
||||||
lib_ios:
|
lib_windows:
|
||||||
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp
|
||||||
|
|
||||||
lib:
|
lib_android_new:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
|
||||||
|
lib_apple_new:
|
||||||
|
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.8
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -259,8 +260,8 @@ publish_docs:
|
|||||||
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
docs_install:
|
docs_install:
|
||||||
python -m venv venv
|
python3 -m venv venv
|
||||||
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
@@ -270,3 +271,6 @@ update:
|
|||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|
||||||
|
%:
|
||||||
|
@:
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import (
|
|||||||
|
|
||||||
type ConnectionManager interface {
|
type ConnectionManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
|
Count() int
|
||||||
|
CloseAll()
|
||||||
|
TrackConn(conn net.Conn) net.Conn
|
||||||
|
TrackPacketConn(conn net.PacketConn) net.PacketConn
|
||||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ type DNSClient interface {
|
|||||||
Start()
|
Start()
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
|
||||||
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
|
||||||
ClearCache()
|
ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +68,7 @@ type DNSTransport interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
|
Reset()
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.EndpointManager = (*Manager)(nil)
|
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||||
@@ -46,10 +48,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, endpoint := range m.endpoints {
|
for _, endpoint := range m.endpoints {
|
||||||
|
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -66,11 +72,15 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,11 +129,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(endpoint, stage)
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ type ClashServer interface {
|
|||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
|
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
|
||||||
HistoryStorage() URLTestHistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ type URLTestHistory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type URLTestHistoryStorage interface {
|
type URLTestHistoryStorage interface {
|
||||||
SetHook(hook chan<- struct{})
|
SetHook(hook *observable.Subscriber[struct{}])
|
||||||
LoadURLTestHistory(tag string) *URLTestHistory
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
DeleteURLTestHistory(tag string)
|
DeleteURLTestHistory(tag string)
|
||||||
StoreURLTestHistory(tag string, history *URLTestHistory)
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
@@ -66,7 +69,11 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
|
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = buffer.Write(s.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -74,7 +81,11 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
|
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = buffer.WriteString(s.LastEtag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -88,7 +99,12 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = varbin.Read(reader, binary.BigEndian, &s.Content)
|
contentLength, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Content = make([]byte, contentLength)
|
||||||
|
_, err = io.ReadFull(reader, s.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,10 +114,16 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.LastUpdated = time.Unix(lastUpdated, 0)
|
s.LastUpdated = time.Unix(lastUpdated, 0)
|
||||||
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
|
etagLength, err := binary.ReadUvarint(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
etagBytes := make([]byte, etagLength)
|
||||||
|
_, err = io.ReadFull(reader, etagBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.LastEtag = string(etagBytes)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -63,13 +63,10 @@ type InboundContext struct {
|
|||||||
// cache
|
// cache
|
||||||
|
|
||||||
// Deprecated: implement in rule action
|
// Deprecated: implement in rule action
|
||||||
InboundDetour string
|
InboundDetour string
|
||||||
LastInbound string
|
LastInbound string
|
||||||
OriginDestination M.Socksaddr
|
OriginDestination M.Socksaddr
|
||||||
RouteOriginalDestination M.Socksaddr
|
RouteOriginalDestination M.Socksaddr
|
||||||
// Deprecated: to be removed
|
|
||||||
//nolint:staticcheck
|
|
||||||
InboundOptions option.InboundOptions
|
|
||||||
UDPDisableDomainUnmapping bool
|
UDPDisableDomainUnmapping bool
|
||||||
UDPConnect bool
|
UDPConnect bool
|
||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
@@ -85,7 +82,9 @@ type InboundContext struct {
|
|||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *ConnectionOwner
|
||||||
|
SourceMACAddress net.HardwareAddr
|
||||||
|
SourceHostname string
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.InboundManager = (*Manager)(nil)
|
var _ adapter.InboundManager = (*Manager)(nil)
|
||||||
@@ -45,10 +47,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
inbounds := m.inbounds
|
inbounds := m.inbounds
|
||||||
m.access.Unlock()
|
m.access.Unlock()
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
|
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -65,11 +71,15 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, inbound.Close(), func(err error) error {
|
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,11 +131,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(inbound, stage)
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
type SimpleLifecycle interface {
|
type SimpleLifecycle interface {
|
||||||
Start() error
|
Start() error
|
||||||
@@ -48,22 +56,47 @@ type LifecycleService interface {
|
|||||||
Lifecycle
|
Lifecycle
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(stage StartStage, services ...Lifecycle) error {
|
func getServiceName(service any) string {
|
||||||
|
if named, ok := service.(interface {
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
}); ok {
|
||||||
|
tag := named.Tag()
|
||||||
|
if tag != "" {
|
||||||
|
return named.Type() + "[" + tag + "]"
|
||||||
|
}
|
||||||
|
return named.Type()
|
||||||
|
}
|
||||||
|
t := reflect.TypeOf(service)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return strings.ToLower(t.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
name := getServiceName(service)
|
||||||
|
logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartNamed(stage StartStage, services []LifecycleService) error {
|
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
logger.Trace(stage, " ", service.Name())
|
||||||
|
startTime := time.Now()
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage.String(), " ", service.Name())
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
}
|
}
|
||||||
|
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
23
adapter/neighbor.go
Normal file
23
adapter/neighbor.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NeighborEntry struct {
|
||||||
|
Address netip.Addr
|
||||||
|
MACAddress net.HardwareAddr
|
||||||
|
Hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NeighborResolver interface {
|
||||||
|
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
|
||||||
|
LookupHostname(address netip.Addr) (string, bool)
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type NeighborUpdateListener interface {
|
||||||
|
UpdateNeighborTable(entries []NeighborEntry)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,10 +83,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
outbounds := m.outbounds
|
outbounds := m.outbounds
|
||||||
m.access.Unlock()
|
m.access.Unlock()
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
|
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -109,22 +115,29 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
|||||||
}
|
}
|
||||||
started[outboundTag] = true
|
started[outboundTag] = true
|
||||||
canContinue = true
|
canContinue = true
|
||||||
|
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
|
||||||
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
m.logger.Trace("start ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("start ", name)
|
||||||
err := starter.Start(adapter.StartStateStart)
|
err := starter.Start(adapter.StartStateStart)
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
return E.Cause(err, "start ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
} else if starter, isStarter := outboundToStart.(interface {
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
Start() error
|
Start() error
|
||||||
}); isStarter {
|
}); isStarter {
|
||||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
m.logger.Trace("start ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("start ", name)
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
return E.Cause(err, "start ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(started) == len(outbounds) {
|
if len(started) == len(outbounds) {
|
||||||
@@ -171,11 +184,15 @@ func (m *Manager) Close() error {
|
|||||||
var err error
|
var err error
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
if closer, isCloser := outbound.(io.Closer); isCloser {
|
if closer, isCloser := outbound.(io.Closer); isCloser {
|
||||||
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -256,11 +273,15 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(outbound, stage)
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
|||||||
74
adapter/platform.go
Normal file
74
adapter/platform.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlatformInterface interface {
|
||||||
|
Initialize(networkManager NetworkManager) error
|
||||||
|
|
||||||
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
|
AutoDetectInterfaceControl(fd int) error
|
||||||
|
|
||||||
|
UsePlatformInterface() bool
|
||||||
|
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
|
|
||||||
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
|
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||||
|
|
||||||
|
UsePlatformNetworkInterfaces() bool
|
||||||
|
NetworkInterfaces() ([]NetworkInterface, error)
|
||||||
|
|
||||||
|
UnderNetworkExtension() bool
|
||||||
|
NetworkExtensionIncludeAllNetworks() bool
|
||||||
|
|
||||||
|
ClearDNSCache()
|
||||||
|
RequestPermissionForWIFIState() error
|
||||||
|
ReadWIFIState() WIFIState
|
||||||
|
SystemCertificates() []string
|
||||||
|
|
||||||
|
UsePlatformConnectionOwnerFinder() bool
|
||||||
|
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
|
||||||
|
|
||||||
|
UsePlatformWIFIMonitor() bool
|
||||||
|
|
||||||
|
UsePlatformNotification() bool
|
||||||
|
SendNotification(notification *Notification) error
|
||||||
|
|
||||||
|
UsePlatformNeighborResolver() bool
|
||||||
|
StartNeighborMonitor(listener NeighborUpdateListener) error
|
||||||
|
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FindConnectionOwnerRequest struct {
|
||||||
|
IpProtocol int32
|
||||||
|
SourceAddress string
|
||||||
|
SourcePort int32
|
||||||
|
DestinationAddress string
|
||||||
|
DestinationPort int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionOwner struct {
|
||||||
|
ProcessID uint32
|
||||||
|
UserId int32
|
||||||
|
UserName string
|
||||||
|
ProcessPath string
|
||||||
|
AndroidPackageName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
Identifier string
|
||||||
|
TypeName string
|
||||||
|
TypeID int32
|
||||||
|
Title string
|
||||||
|
Subtitle string
|
||||||
|
Body string
|
||||||
|
OpenURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemProxyStatus struct {
|
||||||
|
Available bool
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
@@ -21,10 +21,13 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
NeedFindProcess() bool
|
||||||
|
NeedFindNeighbor() bool
|
||||||
|
NeighborResolver() NeighborResolver
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.ServiceManager = (*Manager)(nil)
|
var _ adapter.ServiceManager = (*Manager)(nil)
|
||||||
@@ -43,10 +45,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
services := m.services
|
services := m.services
|
||||||
m.access.Unlock()
|
m.access.Unlock()
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err := adapter.LegacyStart(service, stage)
|
err := adapter.LegacyStart(service, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -63,11 +69,15 @@ func (m *Manager) Close() error {
|
|||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
var err error
|
var err error
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
||||||
|
m.logger.Trace("close ", name)
|
||||||
|
startTime := time.Now()
|
||||||
|
monitor.Start("close ", name)
|
||||||
err = E.Append(err, service.Close(), func(err error) error {
|
err = E.Append(err, service.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
return E.Cause(err, "close ", name)
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -116,11 +126,15 @@ func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag stri
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
|
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
m.logger.Trace(stage, " ", name)
|
||||||
|
startTime := time.Now()
|
||||||
err = adapter.LegacyStart(service, stage)
|
err = adapter.LegacyStart(service, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
return E.Cause(err, stage, " ", name)
|
||||||
}
|
}
|
||||||
|
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
||||||
|
|||||||
61
box.go
61
box.go
@@ -22,7 +22,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
@@ -126,7 +125,10 @@ func New(options Options) (*Box, error) {
|
|||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var needCacheFile bool
|
var needCacheFile bool
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
@@ -139,7 +141,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
@@ -444,15 +446,15 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -464,27 +466,27 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -498,17 +500,42 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
var err error
|
||||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
for _, closeItem := range []struct {
|
||||||
)
|
name string
|
||||||
|
service adapter.Lifecycle
|
||||||
|
}{
|
||||||
|
{"service", s.service},
|
||||||
|
{"endpoint", s.endpoint},
|
||||||
|
{"inbound", s.inbound},
|
||||||
|
{"outbound", s.outbound},
|
||||||
|
{"router", s.router},
|
||||||
|
{"connection", s.connection},
|
||||||
|
{"dns-router", s.dnsRouter},
|
||||||
|
{"dns-transport", s.dnsTransport},
|
||||||
|
{"network", s.network},
|
||||||
|
} {
|
||||||
|
s.logger.Trace("close ", closeItem.name)
|
||||||
|
startTime := time.Now()
|
||||||
|
err = E.Append(err, closeItem.service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ", closeItem.name)
|
||||||
|
})
|
||||||
|
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
|
}
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.internalService {
|
||||||
|
s.logger.Trace("close ", lifecycleService.Name())
|
||||||
|
startTime := time.Now()
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
|
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
}
|
}
|
||||||
|
s.logger.Trace("close logger")
|
||||||
|
startTime := time.Now()
|
||||||
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close logger")
|
return E.Cause(err, "close logger")
|
||||||
})
|
})
|
||||||
|
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,3 +554,7 @@ func (s *Box) Inbound() adapter.InboundManager {
|
|||||||
func (s *Box) Outbound() adapter.OutboundManager {
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
return s.outbound
|
return s.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) LogFactory() log.Factory {
|
||||||
|
return s.logFactory
|
||||||
|
}
|
||||||
|
|||||||
Submodule clients/android updated: 3b2c371905...0d31ac467f
Submodule clients/apple updated: 84d8cf1757...22dcf646ce
@@ -100,11 +100,32 @@ findVersion:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func publishTestflight(ctx context.Context) error {
|
func publishTestflight(ctx context.Context) error {
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
return E.New("platform required: ios, macos, or tvos")
|
||||||
|
}
|
||||||
|
var platform asc.Platform
|
||||||
|
switch os.Args[2] {
|
||||||
|
case "ios":
|
||||||
|
platform = asc.PlatformIOS
|
||||||
|
case "macos":
|
||||||
|
platform = asc.PlatformMACOS
|
||||||
|
case "tvos":
|
||||||
|
platform = asc.PlatformTVOS
|
||||||
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
|
}
|
||||||
|
|
||||||
tagVersion, err := build_shared.ReadTagVersion()
|
tagVersion, err := build_shared.ReadTagVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
|
|
||||||
|
releaseNotes := F.ToString("sing-box ", tagVersion.String())
|
||||||
|
if len(os.Args) >= 4 {
|
||||||
|
releaseNotes = strings.Join(os.Args[3:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
client := createClient(20 * time.Minute)
|
client := createClient(20 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
@@ -115,97 +136,76 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||||
return it.ID
|
return it.ID
|
||||||
})
|
})
|
||||||
var platforms []asc.Platform
|
|
||||||
if len(os.Args) == 3 {
|
|
||||||
switch os.Args[2] {
|
|
||||||
case "ios":
|
|
||||||
platforms = []asc.Platform{asc.PlatformIOS}
|
|
||||||
case "macos":
|
|
||||||
platforms = []asc.Platform{asc.PlatformMACOS}
|
|
||||||
case "tvos":
|
|
||||||
platforms = []asc.Platform{asc.PlatformTVOS}
|
|
||||||
default:
|
|
||||||
return E.New("unknown platform: ", os.Args[2])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
platforms = []asc.Platform{
|
|
||||||
asc.PlatformIOS,
|
|
||||||
asc.PlatformMACOS,
|
|
||||||
asc.PlatformTVOS,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
waitingForProcess := false
|
waitingForProcess := false
|
||||||
for _, platform := range platforms {
|
log.Info(string(platform), " list builds")
|
||||||
log.Info(string(platform), " list builds")
|
for {
|
||||||
for {
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
FilterApp: []string{appID},
|
||||||
FilterApp: []string{appID},
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
})
|
||||||
})
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
build := builds.Data[0]
|
|
||||||
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if *build.Attributes.ProcessingState != "VALID" {
|
|
||||||
waitingForProcess = true
|
|
||||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " list localizations")
|
|
||||||
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
|
||||||
return *it.Attributes.Locale == "en-US"
|
|
||||||
})
|
|
||||||
if localization.ID == "" {
|
|
||||||
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
|
||||||
}
|
|
||||||
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
|
||||||
log.Info(string(platform), " ", tag, " update localization")
|
|
||||||
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
|
||||||
F.ToString("sing-box ", tagVersion.String()),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
|
||||||
log.Info("waiting for process")
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info(string(platform), " ", tag, " list submissions")
|
|
||||||
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
|
||||||
FilterBuild: []string{build.ID},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(betaSubmissions.Data) == 0 {
|
|
||||||
log.Info(string(platform), " ", tag, " create submission")
|
|
||||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
|
||||||
log.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
build := builds.Data[0]
|
||||||
|
log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")")
|
||||||
|
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
|
waitingForProcess = true
|
||||||
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list localizations")
|
||||||
|
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
|
||||||
|
return *it.Attributes.Locale == "en-US"
|
||||||
|
})
|
||||||
|
if localization.ID == "" {
|
||||||
|
log.Fatal(string(platform), " ", tag, " no en-US localization found")
|
||||||
|
}
|
||||||
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
|
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
|
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
||||||
|
log.Info("waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info(string(platform), " ", tag, " list submissions")
|
||||||
|
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
||||||
|
FilterBuild: []string{build.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(betaSubmissions.Data) == 0 {
|
||||||
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
|
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
||||||
|
log.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/gomobile"
|
||||||
@@ -16,17 +17,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
withTailscale bool
|
// withTailscale bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
// flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -46,8 +47,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
macOSTags []string
|
darwinTags []string
|
||||||
memcTags []string
|
// memcTags []string
|
||||||
notMemcTags []string
|
notMemcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
@@ -59,19 +60,38 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -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_utls", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
||||||
macOSTags = append(macOSTags, "with_dhcp")
|
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
// memcTags = append(memcTags, "with_tailscale")
|
||||||
|
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid() {
|
type AndroidBuildConfig struct {
|
||||||
build_shared.FindSDK()
|
AndroidAPI int
|
||||||
|
OutputName string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterTags(tags []string, exclude ...string) []string {
|
||||||
|
excludeMap := make(map[string]bool)
|
||||||
|
for _, tag := range exclude {
|
||||||
|
excludeMap[tag] = true
|
||||||
|
}
|
||||||
|
var result []string
|
||||||
|
for _, tag := range tags {
|
||||||
|
if !excludeMap[tag] {
|
||||||
|
result = append(result, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkJavaVersion() {
|
||||||
var javaPath string
|
var javaPath string
|
||||||
javaHome := os.Getenv("JAVA_HOME")
|
javaHome := os.Getenv("JAVA_HOME")
|
||||||
if javaHome == "" {
|
if javaHome == "" {
|
||||||
@@ -87,21 +107,24 @@ func buildAndroid() {
|
|||||||
if !strings.Contains(javaVersion, "openjdk 17") {
|
if !strings.Contains(javaVersion, "openjdk 17") {
|
||||||
log.Fatal("java version should be openjdk 17")
|
log.Fatal("java version should be openjdk 17")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var bindTarget string
|
func getAndroidBindTarget() string {
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
bindTarget = platform
|
return platform
|
||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
bindTarget = "android/arm64"
|
return "android/arm64"
|
||||||
} else {
|
|
||||||
bindTarget = "android"
|
|
||||||
}
|
}
|
||||||
|
return "android"
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {
|
||||||
args := []string{
|
args := []string{
|
||||||
"bind",
|
"bind",
|
||||||
"-v",
|
"-v",
|
||||||
|
"-o", config.OutputName,
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-androidapi", "21",
|
"-androidapi", strconv.Itoa(config.AndroidAPI),
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
@@ -112,34 +135,59 @@ func buildAndroid() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, memcTags...)
|
args = append(args, "-tags", strings.Join(config.Tags, ","))
|
||||||
if debugEnabled {
|
|
||||||
tags = append(tags, debugTags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "-tags", strings.Join(tags, ","))
|
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
command.Stderr = os.Stderr
|
command.Stderr = os.Stderr
|
||||||
err = command.Run()
|
err := command.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = "libbox.aar"
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||||
if rw.IsDir(copyPath) {
|
if rw.IsDir(copyPath) {
|
||||||
copyPath, _ = filepath.Abs(copyPath)
|
copyPath, _ = filepath.Abs(copyPath)
|
||||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
err = rw.CopyFile(config.OutputName, filepath.Join(copyPath, config.OutputName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Info("copied to ", copyPath)
|
log.Info("copied ", config.OutputName, " to ", copyPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildAndroid() {
|
||||||
|
build_shared.FindSDK()
|
||||||
|
checkJavaVersion()
|
||||||
|
|
||||||
|
bindTarget := getAndroidBindTarget()
|
||||||
|
|
||||||
|
// Build main variant (SDK 23)
|
||||||
|
mainTags := append([]string{}, sharedTags...)
|
||||||
|
// mainTags = append(mainTags, memcTags...)
|
||||||
|
if debugEnabled {
|
||||||
|
mainTags = append(mainTags, debugTags...)
|
||||||
|
}
|
||||||
|
buildAndroidVariant(AndroidBuildConfig{
|
||||||
|
AndroidAPI: 23,
|
||||||
|
OutputName: "libbox.aar",
|
||||||
|
Tags: mainTags,
|
||||||
|
}, bindTarget)
|
||||||
|
|
||||||
|
// Build legacy variant (SDK 21, no naive outbound)
|
||||||
|
legacyTags := filterTags(sharedTags, "with_naive_outbound")
|
||||||
|
// legacyTags = append(legacyTags, memcTags...)
|
||||||
|
if debugEnabled {
|
||||||
|
legacyTags = append(legacyTags, debugTags...)
|
||||||
|
}
|
||||||
|
buildAndroidVariant(AndroidBuildConfig{
|
||||||
|
AndroidAPI: 21,
|
||||||
|
OutputName: "libbox-legacy.aar",
|
||||||
|
Tags: legacyTags,
|
||||||
|
}, bindTarget)
|
||||||
|
}
|
||||||
|
|
||||||
func buildApple() {
|
func buildApple() {
|
||||||
var bindTarget string
|
var bindTarget string
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
@@ -147,7 +195,7 @@ func buildApple() {
|
|||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
bindTarget = "ios"
|
bindTarget = "ios"
|
||||||
} else {
|
} else {
|
||||||
bindTarget = "ios,tvos,macos"
|
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
@@ -157,11 +205,9 @@ func buildApple() {
|
|||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-not-macos=with_low_memory",
|
||||||
}
|
}
|
||||||
if !withTailscale {
|
//if !withTailscale {
|
||||||
args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ","))
|
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
||||||
} else {
|
//}
|
||||||
args = append(args, "-tags-macos="+strings.Join(macOSTags, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
@@ -169,10 +215,10 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := sharedTags
|
tags := append(sharedTags, darwinTags...)
|
||||||
if withTailscale {
|
//if withTailscale {
|
||||||
tags = append(tags, memcTags...)
|
// tags = append(tags, memcTags...)
|
||||||
}
|
//}
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
117
cmd/internal/format_docs/main.go
Normal file
117
cmd/internal/format_docs/main.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := filepath.Walk("docs", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(path, ".md") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return processFile(path)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFile(path string) error {
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(content), "\n")
|
||||||
|
modified := false
|
||||||
|
result := make([]string, 0, len(lines))
|
||||||
|
|
||||||
|
inQuoteBlock := false
|
||||||
|
materialLines := []int{} // indices of :material- lines in the block
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
// Check for quote block start
|
||||||
|
if strings.HasPrefix(line, "!!! quote \"") && strings.Contains(line, "sing-box") {
|
||||||
|
inQuoteBlock = true
|
||||||
|
materialLines = nil
|
||||||
|
result = append(result, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inside a quote block
|
||||||
|
if inQuoteBlock {
|
||||||
|
trimmed := strings.TrimPrefix(line, " ")
|
||||||
|
isMaterialLine := strings.HasPrefix(trimmed, ":material-")
|
||||||
|
isEmpty := strings.TrimSpace(line) == ""
|
||||||
|
isIndented := strings.HasPrefix(line, " ")
|
||||||
|
|
||||||
|
if isMaterialLine {
|
||||||
|
materialLines = append(materialLines, len(result))
|
||||||
|
result = append(result, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block ends when:
|
||||||
|
// - Empty line AFTER we've seen material lines, OR
|
||||||
|
// - Non-indented, non-empty line
|
||||||
|
blockEnds := (isEmpty && len(materialLines) > 0) || (!isEmpty && !isIndented)
|
||||||
|
if blockEnds {
|
||||||
|
// Process collected material lines
|
||||||
|
if len(materialLines) > 0 {
|
||||||
|
for j, idx := range materialLines {
|
||||||
|
isLast := j == len(materialLines)-1
|
||||||
|
resultLine := strings.TrimRight(result[idx], " ")
|
||||||
|
if !isLast {
|
||||||
|
// Add trailing two spaces for non-last lines
|
||||||
|
resultLine += " "
|
||||||
|
}
|
||||||
|
if result[idx] != resultLine {
|
||||||
|
modified = true
|
||||||
|
result[idx] = resultLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inQuoteBlock = false
|
||||||
|
materialLines = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where file ends while still in a block
|
||||||
|
if inQuoteBlock && len(materialLines) > 0 {
|
||||||
|
for j, idx := range materialLines {
|
||||||
|
isLast := j == len(materialLines)-1
|
||||||
|
resultLine := strings.TrimRight(result[idx], " ")
|
||||||
|
if !isLast {
|
||||||
|
resultLine += " "
|
||||||
|
}
|
||||||
|
if result[idx] != resultLine {
|
||||||
|
modified = true
|
||||||
|
result[idx] = resultLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if modified {
|
||||||
|
newContent := strings.Join(result, "\n")
|
||||||
|
if !bytes.Equal(content, []byte(newContent)) {
|
||||||
|
log.Info("formatted: ", path)
|
||||||
|
return os.WriteFile(path, []byte(newContent), 0o644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -71,12 +71,12 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi
|
|||||||
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
|
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
|
||||||
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
|
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
|
||||||
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
|
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
|
||||||
version := projectContent[versionStart:versionEnd]
|
version := strings.Trim(projectContent[versionStart:versionEnd], "\"")
|
||||||
if version == newVersion {
|
if version == newVersion {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
updated = true
|
updated = true
|
||||||
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
|
projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:]
|
||||||
}
|
}
|
||||||
return projectContent, updated
|
return projectContent, updated
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
err = updateChromeIncludedRootCAs()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMozillaIncludedRootCAs() error {
|
func updateMozillaIncludedRootCAs() error {
|
||||||
@@ -69,3 +73,94 @@ func init() {
|
|||||||
generated.WriteString("}\n")
|
generated.WriteString("}\n")
|
||||||
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchChinaFingerprints() (map[string]bool, error) {
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/AllCertificateRecordsCSVFormatv4")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countryIndex := slices.Index(header, "Country")
|
||||||
|
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
||||||
|
|
||||||
|
chinaFingerprints := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if record[countryIndex] == "China" {
|
||||||
|
chinaFingerprints[record[fingerprintIndex]] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chinaFingerprints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChromeIncludedRootCAs() error {
|
||||||
|
chinaFingerprints, err := fetchChinaFingerprints()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/ccadb/RootCACertificatesIncludedByRSReportCSV")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
subjectIndex := slices.Index(header, "Subject")
|
||||||
|
statusIndex := slices.Index(header, "Google Chrome Status")
|
||||||
|
certIndex := slices.Index(header, "X.509 Certificate (PEM)")
|
||||||
|
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
||||||
|
|
||||||
|
generated := strings.Builder{}
|
||||||
|
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||||
|
|
||||||
|
package certificate
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
var chromeIncluded *x509.CertPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
chromeIncluded = x509.NewCertPool()
|
||||||
|
`)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record[statusIndex] != "Included" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if chinaFingerprints[record[fingerprintIndex]] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
generated.WriteString("\n // ")
|
||||||
|
generated.WriteString(record[subjectIndex])
|
||||||
|
generated.WriteString("\n")
|
||||||
|
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
|
cert := record[certIndex]
|
||||||
|
// Remove single quotes if present
|
||||||
|
if len(cert) > 0 && cert[0] == '\'' {
|
||||||
|
cert = cert[1 : len(cert)-1]
|
||||||
|
}
|
||||||
|
generated.WriteString(cert)
|
||||||
|
generated.WriteString("`))\n")
|
||||||
|
}
|
||||||
|
generated.WriteString("}\n")
|
||||||
|
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
|
||||||
|
}
|
||||||
|
|||||||
2817
common/certificate/chrome.go
Normal file
2817
common/certificate/chrome.go
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
@@ -36,7 +35,7 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
switch options.Store {
|
switch options.Store {
|
||||||
case C.CertificateStoreSystem, "":
|
case C.CertificateStoreSystem, "":
|
||||||
systemPool = x509.NewCertPool()
|
systemPool = x509.NewCertPool()
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
var systemValid bool
|
var systemValid bool
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
for _, cert := range platformInterface.SystemCertificates() {
|
for _, cert := range platformInterface.SystemCertificates() {
|
||||||
@@ -54,6 +53,8 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
}
|
}
|
||||||
case C.CertificateStoreMozilla:
|
case C.CertificateStoreMozilla:
|
||||||
systemPool = mozillaIncluded
|
systemPool = mozillaIncluded
|
||||||
|
case C.CertificateStoreChrome:
|
||||||
|
systemPool = chromeIncluded
|
||||||
case C.CertificateStoreNone:
|
case C.CertificateStoreNone:
|
||||||
systemPool = nil
|
systemPool = nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
|
||||||
connAccess.Lock()
|
|
||||||
element := openConnection.PushBack(conn)
|
|
||||||
connAccess.Unlock()
|
|
||||||
if KillerEnabled {
|
|
||||||
err := KillerCheck()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
element: element,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Close() error {
|
|
||||||
if c.element.Value != nil {
|
|
||||||
connAccess.Lock()
|
|
||||||
if c.element.Value != nil {
|
|
||||||
openConnection.Remove(c.element)
|
|
||||||
c.element.Value = nil
|
|
||||||
}
|
|
||||||
connAccess.Unlock()
|
|
||||||
}
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
runtimeDebug "runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/memory"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
KillerEnabled bool
|
|
||||||
MemoryLimit uint64
|
|
||||||
killerLastCheck time.Time
|
|
||||||
)
|
|
||||||
|
|
||||||
func KillerCheck() error {
|
|
||||||
if !KillerEnabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
nowTime := time.Now()
|
|
||||||
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
killerLastCheck = nowTime
|
|
||||||
if memory.Total() > MemoryLimit {
|
|
||||||
Close()
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
runtimeDebug.FreeOSMemory()
|
|
||||||
}()
|
|
||||||
return E.New("out of memory")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PacketConn struct {
|
|
||||||
net.PacketConn
|
|
||||||
element *list.Element[io.Closer]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
|
||||||
connAccess.Lock()
|
|
||||||
element := openConnection.PushBack(conn)
|
|
||||||
connAccess.Unlock()
|
|
||||||
if KillerEnabled {
|
|
||||||
err := KillerCheck()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &PacketConn{
|
|
||||||
PacketConn: conn,
|
|
||||||
element: element,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Close() error {
|
|
||||||
if c.element.Value != nil {
|
|
||||||
connAccess.Lock()
|
|
||||||
if c.element.Value != nil {
|
|
||||||
openConnection.Remove(c.element)
|
|
||||||
c.element.Value = nil
|
|
||||||
}
|
|
||||||
connAccess.Unlock()
|
|
||||||
}
|
|
||||||
return c.PacketConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
|
||||||
return bufio.NewPacketConn(c.PacketConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WriterReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package conntrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
connAccess sync.RWMutex
|
|
||||||
openConnection list.List[io.Closer]
|
|
||||||
)
|
|
||||||
|
|
||||||
func Count() int {
|
|
||||||
if !Enabled {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return openConnection.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func List() []io.Closer {
|
|
||||||
if !Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
connAccess.RLock()
|
|
||||||
defer connAccess.RUnlock()
|
|
||||||
connList := make([]io.Closer, 0, openConnection.Len())
|
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
|
||||||
connList = append(connList, element.Value)
|
|
||||||
}
|
|
||||||
return connList
|
|
||||||
}
|
|
||||||
|
|
||||||
func Close() {
|
|
||||||
if !Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
connAccess.Lock()
|
|
||||||
defer connAccess.Unlock()
|
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
|
||||||
common.Close(element.Value)
|
|
||||||
element.Value = nil
|
|
||||||
}
|
|
||||||
openConnection.Init()
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build !with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = true
|
|
||||||
@@ -9,10 +9,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/common/listener"
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -38,6 +36,7 @@ type DefaultDialer struct {
|
|||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
netns string
|
netns string
|
||||||
|
connectionManager adapter.ConnectionManager
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
defaultNetworkStrategy bool
|
defaultNetworkStrategy bool
|
||||||
@@ -48,8 +47,9 @@ type DefaultDialer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
|
connectionManager := service.FromContext[adapter.ConnectionManager](ctx)
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialer net.Dialer
|
dialer net.Dialer
|
||||||
@@ -90,7 +90,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if defaultOptions.BindInterface != "" {
|
if defaultOptions.BindInterface != "" && !disableDefaultBind {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@@ -138,6 +138,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
||||||
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
||||||
}
|
}
|
||||||
|
if options.BindAddressNoPort {
|
||||||
|
if !C.IsLinux {
|
||||||
|
return nil, E.New("`bind_address_no_port` is only supported on Linux")
|
||||||
|
}
|
||||||
|
dialer.Control = control.Append(dialer.Control, control.BindAddressNoPort())
|
||||||
|
}
|
||||||
if options.ConnectTimeout != 0 {
|
if options.ConnectTimeout != 0 {
|
||||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||||
} else {
|
} else {
|
||||||
@@ -152,8 +158,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if keepInterval == 0 {
|
if keepInterval == 0 {
|
||||||
keepInterval = C.TCPKeepAliveInterval
|
keepInterval = C.TCPKeepAliveInterval
|
||||||
}
|
}
|
||||||
dialer.KeepAlive = keepIdle
|
dialer.KeepAliveConfig = net.KeepAliveConfig{
|
||||||
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval))
|
Enable: true,
|
||||||
|
Idle: keepIdle,
|
||||||
|
Interval: keepInterval,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
@@ -201,6 +210,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
netns: options.NetNs,
|
netns: options.NetNs,
|
||||||
|
connectionManager: connectionManager,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
@@ -233,7 +243,7 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
return nil, E.New("domain not resolved")
|
return nil, E.New("domain not resolved")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
@@ -298,12 +308,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
if !fastFallback && !isPrimary {
|
if !fastFallback && !isPrimary {
|
||||||
d.networkLastFallback.Store(time.Now())
|
d.networkLastFallback.Store(time.Now())
|
||||||
}
|
}
|
||||||
return trackConn(conn, nil)
|
return d.trackConn(conn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
||||||
if destination.IsIPv6() {
|
if destination.IsIPv6() {
|
||||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
@@ -355,23 +365,23 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return trackPacketConn(packetConn, nil)
|
return d.trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) WireGuardControl() control.Func {
|
func (d *DefaultDialer) WireGuardControl() control.Func {
|
||||||
return d.udpListener.Control
|
return d.udpListener.Control
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
if !conntrack.Enabled || err != nil {
|
if d.connectionManager == nil || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return conntrack.NewConn(conn)
|
return d.connectionManager.TrackConn(conn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||||
if !conntrack.Enabled || err != nil {
|
if d.connectionManager == nil || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return conntrack.NewPacketConn(conn)
|
return d.connectionManager.TrackPacketConn(conn), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,3 +145,7 @@ type ParallelNetworkDialer interface {
|
|||||||
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||||
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketDialerWithDestination interface {
|
||||||
|
ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error)
|
||||||
|
}
|
||||||
|
|||||||
234
common/geosite/compat_test.go
Normal file
234
common/geosite/compat_test.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package geosite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Old implementation using varbin reflection-based serialization
|
||||||
|
|
||||||
|
func oldWriteString(writer varbin.Writer, value string) error {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldWriteItem(writer varbin.Writer, item Item) error {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.Write(writer, binary.BigEndian, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldReadString(reader varbin.Reader) (string, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.ReadValue[string](reader, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldReadItem(reader varbin.Reader) (Item, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.ReadValue[Item](reader, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{"empty", ""},
|
||||||
|
{"single_char", "a"},
|
||||||
|
{"ascii", "example.com"},
|
||||||
|
{"utf8", "测试域名.中国"},
|
||||||
|
{"special_chars", "\x00\xff\n\t"},
|
||||||
|
{"127_bytes", strings.Repeat("x", 127)},
|
||||||
|
{"128_bytes", strings.Repeat("x", 128)},
|
||||||
|
{"16383_bytes", strings.Repeat("x", 16383)},
|
||||||
|
{"16384_bytes", strings.Repeat("x", 16384)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Old write
|
||||||
|
var oldBuf bytes.Buffer
|
||||||
|
err := oldWriteString(&oldBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err = writeString(&newBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bytes must match
|
||||||
|
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
||||||
|
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
||||||
|
|
||||||
|
// New write -> old read
|
||||||
|
readBack, err := oldReadString(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// Old write -> new read
|
||||||
|
readBack2, err := readString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.input, readBack2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestItemCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Note: varbin.Write has a bug where struct values (not pointers) don't write their fields
|
||||||
|
// because field.CanSet() returns false for non-addressable values.
|
||||||
|
// The old geosite code passed Item values to varbin.Write, which silently wrote nothing.
|
||||||
|
// The new code correctly writes Type + Value using manual serialization.
|
||||||
|
// This test verifies the new serialization format and round-trip correctness.
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input Item
|
||||||
|
}{
|
||||||
|
{"domain_empty", Item{Type: RuleTypeDomain, Value: ""}},
|
||||||
|
{"domain_normal", Item{Type: RuleTypeDomain, Value: "example.com"}},
|
||||||
|
{"domain_suffix", Item{Type: RuleTypeDomainSuffix, Value: ".example.com"}},
|
||||||
|
{"domain_keyword", Item{Type: RuleTypeDomainKeyword, Value: "google"}},
|
||||||
|
{"domain_regex", Item{Type: RuleTypeDomainRegex, Value: `^.*\.example\.com$`}},
|
||||||
|
{"utf8_domain", Item{Type: RuleTypeDomain, Value: "测试.com"}},
|
||||||
|
{"long_domain", Item{Type: RuleTypeDomainSuffix, Value: strings.Repeat("a", 200) + ".com"}},
|
||||||
|
{"128_bytes_value", Item{Type: RuleTypeDomain, Value: strings.Repeat("x", 128)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err := newBuf.WriteByte(byte(tc.input.Type))
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = writeString(&newBuf, tc.input.Value)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify format: Type (1 byte) + Value (uvarint len + bytes)
|
||||||
|
require.True(t, len(newBuf.Bytes()) >= 1, "output too short")
|
||||||
|
require.Equal(t, byte(tc.input.Type), newBuf.Bytes()[0], "type byte mismatch")
|
||||||
|
|
||||||
|
// New write -> old read (varbin can read correctly when given addressable target)
|
||||||
|
readBack, err := oldReadItem(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// New write -> new read
|
||||||
|
reader := bufio.NewReader(bytes.NewReader(newBuf.Bytes()))
|
||||||
|
typeByte, err := reader.ReadByte()
|
||||||
|
require.NoError(t, err)
|
||||||
|
value, err := readString(reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.input, Item{Type: ItemType(typeByte), Value: value})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeositeWriteReadCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input map[string][]Item
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"empty_map",
|
||||||
|
map[string][]Item{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single_code_empty_items",
|
||||||
|
map[string][]Item{"test": {}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single_code_single_item",
|
||||||
|
map[string][]Item{"test": {{Type: RuleTypeDomain, Value: "a.com"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"single_code_multi_items",
|
||||||
|
map[string][]Item{
|
||||||
|
"test": {
|
||||||
|
{Type: RuleTypeDomain, Value: "a.com"},
|
||||||
|
{Type: RuleTypeDomainSuffix, Value: ".b.com"},
|
||||||
|
{Type: RuleTypeDomainKeyword, Value: "keyword"},
|
||||||
|
{Type: RuleTypeDomainRegex, Value: `^.*$`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multi_code",
|
||||||
|
map[string][]Item{
|
||||||
|
"cn": {{Type: RuleTypeDomain, Value: "baidu.com"}, {Type: RuleTypeDomainSuffix, Value: ".cn"}},
|
||||||
|
"us": {{Type: RuleTypeDomain, Value: "google.com"}},
|
||||||
|
"jp": {{Type: RuleTypeDomainSuffix, Value: ".jp"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"utf8_values",
|
||||||
|
map[string][]Item{
|
||||||
|
"test": {
|
||||||
|
{Type: RuleTypeDomain, Value: "测试.中国"},
|
||||||
|
{Type: RuleTypeDomainSuffix, Value: ".テスト"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"large_items",
|
||||||
|
generateLargeItems(1000),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Write using new implementation
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := Write(&buf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Read back and verify
|
||||||
|
reader, codes, err := NewReader(bytes.NewReader(buf.Bytes()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify all codes exist
|
||||||
|
codeSet := make(map[string]bool)
|
||||||
|
for _, code := range codes {
|
||||||
|
codeSet[code] = true
|
||||||
|
}
|
||||||
|
for code := range tc.input {
|
||||||
|
require.True(t, codeSet[code], "missing code: %s", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify items match
|
||||||
|
for code, expectedItems := range tc.input {
|
||||||
|
items, err := reader.Read(code)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedItems, items, "items mismatch for code: %s", code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateLargeItems(count int) map[string][]Item {
|
||||||
|
items := make([]Item, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
items[i] = Item{
|
||||||
|
Type: ItemType(i % 4),
|
||||||
|
Value: strings.Repeat("x", i%200) + ".com",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map[string][]Item{"large": items}
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
@@ -78,7 +77,7 @@ func (r *Reader) readMetadata() error {
|
|||||||
codeIndex uint64
|
codeIndex uint64
|
||||||
codeLength uint64
|
codeLength uint64
|
||||||
)
|
)
|
||||||
code, err = varbin.ReadValue[string](reader, binary.BigEndian)
|
code, err = readString(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -112,9 +111,16 @@ func (r *Reader) Read(code string) ([]Item, error) {
|
|||||||
}
|
}
|
||||||
r.bufferedReader.Reset(r.reader)
|
r.bufferedReader.Reset(r.reader)
|
||||||
itemList := make([]Item, r.domainLength[code])
|
itemList := make([]Item, r.domainLength[code])
|
||||||
err = varbin.Read(r.bufferedReader, binary.BigEndian, &itemList)
|
for i := range itemList {
|
||||||
if err != nil {
|
typeByte, err := r.bufferedReader.ReadByte()
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
itemList[i].Type = ItemType(typeByte)
|
||||||
|
itemList[i].Value, err = readString(r.bufferedReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return itemList, nil
|
return itemList, nil
|
||||||
}
|
}
|
||||||
@@ -135,3 +141,18 @@ func (r *readCounter) Read(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readString(reader io.ByteReader) (string, error) {
|
||||||
|
length, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
bytes := make([]byte, length)
|
||||||
|
for i := range bytes {
|
||||||
|
bytes[i], err = reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(bytes), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package geosite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
@@ -20,7 +19,11 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
for _, code := range keys {
|
for _, code := range keys {
|
||||||
index[code] = content.Len()
|
index[code] = content.Len()
|
||||||
for _, item := range domains[code] {
|
for _, item := range domains[code] {
|
||||||
err := varbin.Write(content, binary.BigEndian, item)
|
err := content.WriteByte(byte(item.Type))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writeString(content, item.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -38,7 +41,7 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, code := range keys {
|
for _, code := range keys {
|
||||||
err = varbin.Write(writer, binary.BigEndian, code)
|
err = writeString(writer, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -59,3 +62,12 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeString(writer varbin.Writer, value string) error {
|
||||||
|
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(value))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return common.DefaultValue[T](), E.Cause(err, "get current netns")
|
return common.DefaultValue[T](), E.Cause(err, "get current netns")
|
||||||
}
|
}
|
||||||
|
defer currentNs.Close()
|
||||||
defer netns.Set(currentNs)
|
defer netns.Set(currentNs)
|
||||||
var targetNs netns.NsHandle
|
var targetNs netns.NsHandle
|
||||||
if strings.HasPrefix(nameOrPath, "/") {
|
if strings.HasPrefix(nameOrPath, "/") {
|
||||||
|
|||||||
@@ -99,8 +99,6 @@ func (l *Listener) loopTCPIn() {
|
|||||||
}
|
}
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
metadata.InboundDetour = l.listenOptions.Detour
|
metadata.InboundDetour = l.listenOptions.Detour
|
||||||
//nolint:staticcheck
|
|
||||||
metadata.InboundOptions = l.listenOptions.InboundOptions
|
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
ctx := log.ContextWithNewID(l.ctx)
|
ctx := log.ContextWithNewID(l.ctx)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Searcher interface {
|
type Searcher interface {
|
||||||
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error)
|
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNotFound = E.New("process not found")
|
var ErrNotFound = E.New("process not found")
|
||||||
@@ -22,15 +23,7 @@ type Config struct {
|
|||||||
PackageManager tun.PackageManager
|
PackageManager tun.PackageManager
|
||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
ProcessID uint32
|
|
||||||
ProcessPath string
|
|
||||||
PackageName string
|
|
||||||
User string
|
|
||||||
UserId int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
|
||||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -38,7 +31,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou
|
|||||||
if info.UserId != -1 {
|
if info.UserId != -1 {
|
||||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||||
if osUser != nil {
|
if osUser != nil {
|
||||||
info.User = osUser.Username
|
info.UserName = osUser.Username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return info, nil
|
return info, nil
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,22 +18,22 @@ func NewSearcher(config Config) (Searcher, error) {
|
|||||||
return &androidSearcher{config.PackageManager}, nil
|
return &androidSearcher{config.PackageManager}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
_, uid, err := resolveSocketByNetlink(network, source, destination)
|
_, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
|
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
|
||||||
return &Info{
|
return &adapter.ConnectionOwner{
|
||||||
UserId: int32(uid),
|
UserId: int32(uid),
|
||||||
PackageName: sharedPackage,
|
AndroidPackageName: sharedPackage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
|
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
|
||||||
return &Info{
|
return &adapter.ConnectionOwner{
|
||||||
UserId: int32(uid),
|
UserId: int32(uid),
|
||||||
PackageName: packageName,
|
AndroidPackageName: packageName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &Info{UserId: int32(uid)}, nil
|
return &adapter.ConnectionOwner{UserId: int32(uid)}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@@ -23,12 +24,12 @@ func NewSearcher(_ Config) (Searcher, error) {
|
|||||||
return &darwinSearcher{}, nil
|
return &darwinSearcher{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Info{ProcessPath: processName, UserId: -1}, nil
|
return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var structSize = func() int {
|
var structSize = func() int {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ func NewSearcher(config Config) (Searcher, error) {
|
|||||||
return &linuxSearcher{config.Logger}, nil
|
return &linuxSearcher{config.Logger}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
inode, uid, err := resolveSocketByNetlink(network, source, destination)
|
inode, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -28,7 +29,7 @@ func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, sou
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.DebugContext(ctx, "find process path: ", err)
|
s.logger.DebugContext(ctx, "find process path: ", err)
|
||||||
}
|
}
|
||||||
return &Info{
|
return &adapter.ConnectionOwner{
|
||||||
UserId: int32(uid),
|
UserId: int32(uid),
|
||||||
ProcessPath: processPath,
|
ProcessPath: processPath,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/winiphlpapi"
|
"github.com/sagernet/sing/common/winiphlpapi"
|
||||||
|
|
||||||
@@ -27,16 +28,16 @@ func initWin32API() error {
|
|||||||
return winiphlpapi.LoadExtendedTable()
|
return winiphlpapi.LoadExtendedTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
pid, err := winiphlpapi.FindPid(network, source)
|
pid, err := winiphlpapi.FindPid(network, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
path, err := getProcessPath(pid)
|
path, err := getProcessPath(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Info{ProcessID: pid, UserId: -1}, err
|
return &adapter.ConnectionOwner{ProcessID: pid, UserId: -1}, err
|
||||||
}
|
}
|
||||||
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
return &adapter.ConnectionOwner{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProcessPath(pid uint32) (string, error) {
|
func getProcessPath(pid uint32) (string, error) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -303,8 +303,6 @@ find:
|
|||||||
metadata.Protocol = C.ProtocolQUIC
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
fingerprint, err := ja3.Compute(buffer.Bytes())
|
fingerprint, err := ja3.Compute(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
metadata.Protocol = C.ProtocolQUIC
|
|
||||||
metadata.Client = C.ClientChromium
|
|
||||||
metadata.SniffContext = fragments
|
metadata.SniffContext = fragments
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
@@ -334,7 +332,7 @@ find:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 {
|
if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 {
|
||||||
if maybeUQUIC(fingerprint) {
|
if isQUICGo(fingerprint) {
|
||||||
metadata.Client = C.ClientQUICGo
|
metadata.Client = C.ClientQUICGo
|
||||||
} else {
|
} else {
|
||||||
metadata.Client = C.ClientChromium
|
metadata.Client = C.ClientChromium
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
package sniff
|
package sniff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/ja3"
|
"github.com/sagernet/sing-box/common/ja3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chromium sends separate client hello packets, but UQUIC has not yet implemented this behavior
|
const (
|
||||||
// The cronet without this behavior does not have version 115
|
// X25519Kyber768Draft00 - post-quantum curve used by Go crypto/tls
|
||||||
var uQUICChrome115 = &ja3.ClientHello{
|
x25519Kyber768Draft00 uint16 = 0x11EC // 4588
|
||||||
Version: tls.VersionTLS12,
|
// renegotiation_info extension used by Go crypto/tls
|
||||||
CipherSuites: []uint16{4865, 4866, 4867},
|
extensionRenegotiationInfo uint16 = 0xFF01 // 65281
|
||||||
Extensions: []uint16{0, 10, 13, 16, 27, 43, 45, 51, 57, 17513},
|
)
|
||||||
EllipticCurves: []uint16{29, 23, 24},
|
|
||||||
SignatureAlgorithms: []uint16{1027, 2052, 1025, 1283, 2053, 1281, 2054, 1537, 513},
|
|
||||||
}
|
|
||||||
|
|
||||||
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
// isQUICGo detects native quic-go by checking for Go crypto/tls specific features.
|
||||||
return !uQUICChrome115.Equals(fingerprint, true)
|
// Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium
|
||||||
|
// since it uses the same TLS fingerprint, so it will be identified as Chromium.
|
||||||
|
func isQUICGo(fingerprint *ja3.ClientHello) bool {
|
||||||
|
for _, curve := range fingerprint.EllipticCurves {
|
||||||
|
if curve == x25519Kyber768Draft00 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ext := range fingerprint.Extensions {
|
||||||
|
if ext == extensionRenegotiationInfo {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
188
common/sniff/quic_capture_test.go
Normal file
188
common/sniff/quic_capture_test.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffQUICQuicGoFingerprint(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const testSNI = "test.example.com"
|
||||||
|
|
||||||
|
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer udpConn.Close()
|
||||||
|
|
||||||
|
serverAddr := udpConn.LocalAddr().(*net.UDPAddr)
|
||||||
|
packetsChan := make(chan [][]byte, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var packets [][]byte
|
||||||
|
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
n, _, err := udpConn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
packets = append(packets, buf[:n])
|
||||||
|
}
|
||||||
|
packetsChan <- packets
|
||||||
|
}()
|
||||||
|
|
||||||
|
clientConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
ServerName: testSNI,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
NextProtos: []string{"h3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case packets := <-packetsChan:
|
||||||
|
t.Logf("Captured %d packets", len(packets))
|
||||||
|
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
for i, pkt := range packets {
|
||||||
|
err := sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
|
t.Logf("Packet %d: err=%v, domain=%s, client=%s", i, err, metadata.Domain, metadata.Client)
|
||||||
|
if metadata.Domain != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("\n=== quic-go TLS Fingerprint Analysis ===")
|
||||||
|
t.Logf("Domain: %s", metadata.Domain)
|
||||||
|
t.Logf("Client: %s", metadata.Client)
|
||||||
|
t.Logf("Protocol: %s", metadata.Protocol)
|
||||||
|
|
||||||
|
// The client should be identified as quic-go, not chromium
|
||||||
|
// Current issue: it's being identified as chromium
|
||||||
|
if metadata.Client == "chromium" {
|
||||||
|
t.Log("WARNING: quic-go is being misidentified as chromium!")
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("Timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffQUICInitialFromQuicGo(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const testSNI = "test.example.com"
|
||||||
|
|
||||||
|
// Create UDP listener to capture ALL initial packets
|
||||||
|
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer udpConn.Close()
|
||||||
|
|
||||||
|
serverAddr := udpConn.LocalAddr().(*net.UDPAddr)
|
||||||
|
|
||||||
|
// Channel to receive captured packets
|
||||||
|
packetsChan := make(chan [][]byte, 1)
|
||||||
|
|
||||||
|
// Start goroutine to capture packets
|
||||||
|
go func() {
|
||||||
|
var packets [][]byte
|
||||||
|
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||||
|
for i := 0; i < 5; i++ { // Capture up to 5 packets
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
n, _, err := udpConn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
packets = append(packets, buf[:n])
|
||||||
|
}
|
||||||
|
packetsChan <- packets
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create QUIC client connection (will fail but we capture the initial packet)
|
||||||
|
clientConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{
|
||||||
|
ServerName: testSNI,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
NextProtos: []string{"h3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// This will fail (no server) but sends initial packet
|
||||||
|
_, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{})
|
||||||
|
|
||||||
|
// Wait for captured packets
|
||||||
|
select {
|
||||||
|
case packets := <-packetsChan:
|
||||||
|
t.Logf("Captured %d QUIC packets", len(packets))
|
||||||
|
|
||||||
|
for i, packet := range packets {
|
||||||
|
t.Logf("Packet %d: length=%d, first 30 bytes: %x", i, len(packet), packet[:min(30, len(packet))])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test sniffer with first packet
|
||||||
|
if len(packets) > 0 {
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err := sniff.QUICClientHello(context.Background(), &metadata, packets[0])
|
||||||
|
|
||||||
|
t.Logf("First packet sniff error: %v", err)
|
||||||
|
t.Logf("Protocol: %s", metadata.Protocol)
|
||||||
|
t.Logf("Domain: %s", metadata.Domain)
|
||||||
|
t.Logf("Client: %s", metadata.Client)
|
||||||
|
|
||||||
|
// If first packet needs more data, try with subsequent packets
|
||||||
|
// IMPORTANT: reuse metadata to accumulate CRYPTO fragments via SniffContext
|
||||||
|
if errors.Is(err, sniff.ErrNeedMoreData) && len(packets) > 1 {
|
||||||
|
t.Log("First packet needs more data, trying subsequent packets with shared context...")
|
||||||
|
for i := 1; i < len(packets); i++ {
|
||||||
|
// Reuse same metadata to accumulate fragments
|
||||||
|
err = sniff.QUICClientHello(context.Background(), &metadata, packets[i])
|
||||||
|
t.Logf("Packet %d sniff result: err=%v, domain=%s, sniffCtx=%v", i, err, metadata.Domain, metadata.SniffContext != nil)
|
||||||
|
if metadata.Domain != "" || (err != nil && !errors.Is(err, sniff.ErrNeedMoreData)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print hex dump for debugging
|
||||||
|
t.Logf("First packet hex:\n%s", hex.Dump(packets[0][:min(256, len(packets[0]))]))
|
||||||
|
|
||||||
|
// Log final results
|
||||||
|
t.Logf("Final: Protocol=%s, Domain=%s, Client=%s", metadata.Protocol, metadata.Domain, metadata.Client)
|
||||||
|
|
||||||
|
// Verify SNI extraction
|
||||||
|
if metadata.Domain == "" {
|
||||||
|
t.Errorf("Failed to extract SNI, expected: %s", testSNI)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, testSNI, metadata.Domain, "SNI should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check client identification - quic-go should be identified as quic-go, not chromium
|
||||||
|
t.Logf("Client identified as: %s (expected: quic-go)", metadata.Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("Timeout waiting for QUIC packets")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ func TestSniffQUICChromeNew(t *testing.T) {
|
|||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Empty(t, metadata.Client)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -39,7 +39,7 @@ func TestSniffQUICChromium(t *testing.T) {
|
|||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Equal(t, metadata.Client, C.ClientChromium)
|
require.Empty(t, metadata.Client)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -505,7 +506,24 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readRuleItemString(reader varbin.Reader) ([]string, error) {
|
func readRuleItemString(reader varbin.Reader) ([]string, error) {
|
||||||
return varbin.ReadValue[[]string](reader, binary.BigEndian)
|
length, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]string, length)
|
||||||
|
for i := range result {
|
||||||
|
strLen, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := make([]byte, strLen)
|
||||||
|
_, err = io.ReadFull(reader, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[i] = string(buf)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
|
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
|
||||||
@@ -513,11 +531,34 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range value {
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(s)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
||||||
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
length, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]E, length)
|
||||||
|
_, err = io.ReadFull(reader, *(*[]byte)(unsafe.Pointer(&result)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
||||||
@@ -525,11 +566,25 @@ func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
length, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := make([]uint16, length)
|
||||||
|
err = binary.Read(reader, binary.BigEndian, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
|
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
|
||||||
@@ -537,7 +592,11 @@ func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Write(writer, binary.BigEndian, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
|
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
|
||||||
|
|||||||
494
common/srs/compat_test.go
Normal file
494
common/srs/compat_test.go
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
package srs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"go4.org/netipx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Old implementations using varbin reflection-based serialization
|
||||||
|
|
||||||
|
func oldWriteStringSlice(writer varbin.Writer, value []string) error {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldReadStringSlice(reader varbin.Reader) ([]string, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.ReadValue[[]string](reader, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldReadUint8Slice[E ~uint8](reader varbin.Reader) ([]E, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldWriteUint16Slice(writer varbin.Writer, value []uint16) error {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldReadUint16Slice(reader varbin.Reader) ([]uint16, error) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldWritePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
||||||
|
//nolint:staticcheck
|
||||||
|
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type oldIPRangeData struct {
|
||||||
|
From []byte
|
||||||
|
To []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The old writeIPSet had a bug where varbin.Write(writer, binary.BigEndian, data)
|
||||||
|
// with a struct VALUE (not pointer) silently wrote nothing because field.CanSet() returned false.
|
||||||
|
// This caused IP range data to be missing from the output.
|
||||||
|
// The new implementation correctly writes all range data.
|
||||||
|
//
|
||||||
|
// The old readIPSet used varbin.Read with a pre-allocated slice, which worked because
|
||||||
|
// slice elements are addressable and CanSet() returns true for them.
|
||||||
|
//
|
||||||
|
// For compatibility testing, we verify:
|
||||||
|
// 1. New write produces correct output with range data
|
||||||
|
// 2. New read can parse the new format correctly
|
||||||
|
// 3. Round-trip works correctly
|
||||||
|
|
||||||
|
func oldReadIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
||||||
|
version, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version != 1 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var length uint64
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ranges := make([]oldIPRangeData, length)
|
||||||
|
//nolint:staticcheck
|
||||||
|
err = varbin.Read(reader, binary.BigEndian, &ranges)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mySet := &myIPSet{
|
||||||
|
rr: make([]myIPRange, len(ranges)),
|
||||||
|
}
|
||||||
|
for i, rangeData := range ranges {
|
||||||
|
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
|
||||||
|
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
|
||||||
|
}
|
||||||
|
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New write functions (without itemType prefix for testing)
|
||||||
|
|
||||||
|
func newWriteStringSlice(writer varbin.Writer, value []string) error {
|
||||||
|
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, s := range value {
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(s)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(s))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error {
|
||||||
|
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWriteUint16Slice(writer varbin.Writer, value []uint16) error {
|
||||||
|
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return binary.Write(writer, binary.BigEndian, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWritePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
||||||
|
addrSlice := prefix.Addr().AsSlice()
|
||||||
|
_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(addrSlice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return writer.WriteByte(uint8(prefix.Bits()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
|
||||||
|
func TestStringSliceCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
}{
|
||||||
|
{"nil", nil},
|
||||||
|
{"empty", []string{}},
|
||||||
|
{"single_empty", []string{""}},
|
||||||
|
{"single", []string{"test"}},
|
||||||
|
{"multi", []string{"a", "b", "c"}},
|
||||||
|
{"with_empty", []string{"a", "", "c"}},
|
||||||
|
{"utf8", []string{"测试", "テスト", "тест"}},
|
||||||
|
{"long_string", []string{strings.Repeat("x", 128)}},
|
||||||
|
{"many_elements", generateStrings(128)},
|
||||||
|
{"many_elements_256", generateStrings(256)},
|
||||||
|
{"127_byte_string", []string{strings.Repeat("x", 127)}},
|
||||||
|
{"128_byte_string", []string{strings.Repeat("x", 128)}},
|
||||||
|
{"mixed_lengths", []string{"a", strings.Repeat("b", 100), "", strings.Repeat("c", 200)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Old write
|
||||||
|
var oldBuf bytes.Buffer
|
||||||
|
err := oldWriteStringSlice(&oldBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err = newWriteStringSlice(&newBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bytes must match
|
||||||
|
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
||||||
|
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
||||||
|
|
||||||
|
// New write -> old read
|
||||||
|
readBack, err := oldReadStringSlice(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireStringSliceEqual(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// Old write -> new read
|
||||||
|
readBack2, err := readRuleItemString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireStringSliceEqual(t, tc.input, readBack2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint8SliceCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input []uint8
|
||||||
|
}{
|
||||||
|
{"nil", nil},
|
||||||
|
{"empty", []uint8{}},
|
||||||
|
{"single_zero", []uint8{0}},
|
||||||
|
{"single_max", []uint8{255}},
|
||||||
|
{"multi", []uint8{0, 1, 127, 128, 255}},
|
||||||
|
{"boundary", []uint8{0x00, 0x7f, 0x80, 0xff}},
|
||||||
|
{"sequential", generateUint8Slice(256)},
|
||||||
|
{"127_elements", generateUint8Slice(127)},
|
||||||
|
{"128_elements", generateUint8Slice(128)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Old write
|
||||||
|
var oldBuf bytes.Buffer
|
||||||
|
err := oldWriteUint8Slice(&oldBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err = newWriteUint8Slice(&newBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bytes must match
|
||||||
|
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
||||||
|
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
||||||
|
|
||||||
|
// New write -> old read
|
||||||
|
readBack, err := oldReadUint8Slice[uint8](bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireUint8SliceEqual(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// Old write -> new read
|
||||||
|
readBack2, err := readRuleItemUint8[uint8](bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireUint8SliceEqual(t, tc.input, readBack2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUint16SliceCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input []uint16
|
||||||
|
}{
|
||||||
|
{"nil", nil},
|
||||||
|
{"empty", []uint16{}},
|
||||||
|
{"single_zero", []uint16{0}},
|
||||||
|
{"single_max", []uint16{65535}},
|
||||||
|
{"multi", []uint16{0, 255, 256, 32767, 32768, 65535}},
|
||||||
|
{"ports", []uint16{80, 443, 8080, 8443}},
|
||||||
|
{"127_elements", generateUint16Slice(127)},
|
||||||
|
{"128_elements", generateUint16Slice(128)},
|
||||||
|
{"256_elements", generateUint16Slice(256)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Old write
|
||||||
|
var oldBuf bytes.Buffer
|
||||||
|
err := oldWriteUint16Slice(&oldBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err = newWriteUint16Slice(&newBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bytes must match
|
||||||
|
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
||||||
|
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
||||||
|
|
||||||
|
// New write -> old read
|
||||||
|
readBack, err := oldReadUint16Slice(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireUint16SliceEqual(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// Old write -> new read
|
||||||
|
readBack2, err := readRuleItemUint16(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireUint16SliceEqual(t, tc.input, readBack2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrefixCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input netip.Prefix
|
||||||
|
}{
|
||||||
|
{"ipv4_0", netip.MustParsePrefix("0.0.0.0/0")},
|
||||||
|
{"ipv4_8", netip.MustParsePrefix("10.0.0.0/8")},
|
||||||
|
{"ipv4_16", netip.MustParsePrefix("192.168.0.0/16")},
|
||||||
|
{"ipv4_24", netip.MustParsePrefix("192.168.1.0/24")},
|
||||||
|
{"ipv4_32", netip.MustParsePrefix("1.2.3.4/32")},
|
||||||
|
{"ipv6_0", netip.MustParsePrefix("::/0")},
|
||||||
|
{"ipv6_64", netip.MustParsePrefix("2001:db8::/64")},
|
||||||
|
{"ipv6_128", netip.MustParsePrefix("::1/128")},
|
||||||
|
{"ipv6_full", netip.MustParsePrefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334/128")},
|
||||||
|
{"ipv4_private", netip.MustParsePrefix("172.16.0.0/12")},
|
||||||
|
{"ipv6_link_local", netip.MustParsePrefix("fe80::/10")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Old write
|
||||||
|
var oldBuf bytes.Buffer
|
||||||
|
err := oldWritePrefix(&oldBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err = newWritePrefix(&newBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bytes must match
|
||||||
|
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
||||||
|
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
||||||
|
|
||||||
|
// New write -> new read (no old read for prefix)
|
||||||
|
readBack, err := readPrefix(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// Old write -> new read
|
||||||
|
readBack2, err := readPrefix(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.input, readBack2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPSetCompat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Note: The old writeIPSet was buggy (varbin.Write with struct values wrote nothing).
|
||||||
|
// This test verifies the new implementation writes correct data and round-trips correctly.
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
input *netipx.IPSet
|
||||||
|
}{
|
||||||
|
{"single_ipv4", buildIPSet("1.2.3.4")},
|
||||||
|
{"ipv4_range", buildIPSet("192.168.0.0/16")},
|
||||||
|
{"multi_ipv4", buildIPSet("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")},
|
||||||
|
{"single_ipv6", buildIPSet("::1")},
|
||||||
|
{"ipv6_range", buildIPSet("2001:db8::/32")},
|
||||||
|
{"mixed", buildIPSet("10.0.0.0/8", "::1", "2001:db8::/32")},
|
||||||
|
{"large", buildLargeIPSet(100)},
|
||||||
|
{"adjacent_ranges", buildIPSet("192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// New write
|
||||||
|
var newBuf bytes.Buffer
|
||||||
|
err := writeIPSet(&newBuf, tc.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify format starts with version byte (1) + uint64 count
|
||||||
|
require.True(t, len(newBuf.Bytes()) >= 9, "output too short")
|
||||||
|
require.Equal(t, byte(1), newBuf.Bytes()[0], "version byte mismatch")
|
||||||
|
|
||||||
|
// New write -> old read (varbin.Read with pre-allocated slice works correctly)
|
||||||
|
readBack, err := oldReadIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireIPSetEqual(t, tc.input, readBack)
|
||||||
|
|
||||||
|
// New write -> new read
|
||||||
|
readBack2, err := readIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
||||||
|
require.NoError(t, err)
|
||||||
|
requireIPSetEqual(t, tc.input, readBack2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
|
func generateStrings(count int) []string {
|
||||||
|
result := make([]string, count)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = strings.Repeat("x", i%50)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateUint8Slice(count int) []uint8 {
|
||||||
|
result := make([]uint8, count)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = uint8(i % 256)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateUint16Slice(count int) []uint16 {
|
||||||
|
result := make([]uint16, count)
|
||||||
|
for i := range result {
|
||||||
|
result[i] = uint16(i * 257)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildIPSet(cidrs ...string) *netipx.IPSet {
|
||||||
|
var builder netipx.IPSetBuilder
|
||||||
|
for _, cidr := range cidrs {
|
||||||
|
prefix, err := netip.ParsePrefix(cidr)
|
||||||
|
if err != nil {
|
||||||
|
addr, err := netip.ParseAddr(cidr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
builder.Add(addr)
|
||||||
|
} else {
|
||||||
|
builder.AddPrefix(prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set, _ := builder.IPSet()
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLargeIPSet(count int) *netipx.IPSet {
|
||||||
|
var builder netipx.IPSetBuilder
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24)
|
||||||
|
builder.AddPrefix(prefix)
|
||||||
|
}
|
||||||
|
set, _ := builder.IPSet()
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireStringSliceEqual(t *testing.T, expected, actual []string) {
|
||||||
|
t.Helper()
|
||||||
|
if len(expected) == 0 && len(actual) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireUint8SliceEqual(t *testing.T, expected, actual []uint8) {
|
||||||
|
t.Helper()
|
||||||
|
if len(expected) == 0 && len(actual) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireUint16SliceEqual(t *testing.T, expected, actual []uint16) {
|
||||||
|
t.Helper()
|
||||||
|
if len(expected) == 0 && len(actual) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireIPSetEqual(t *testing.T, expected, actual *netipx.IPSet) {
|
||||||
|
t.Helper()
|
||||||
|
expectedRanges := expected.Ranges()
|
||||||
|
actualRanges := actual.Ranges()
|
||||||
|
require.Equal(t, len(expectedRanges), len(actualRanges), "range count mismatch")
|
||||||
|
for i := range expectedRanges {
|
||||||
|
require.Equal(t, expectedRanges[i].From(), actualRanges[i].From(), "range[%d].from mismatch", i)
|
||||||
|
require.Equal(t, expectedRanges[i].To(), actualRanges[i].To(), "range[%d].to mismatch", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package srs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -9,11 +10,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
||||||
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
|
addrLen, err := binary.ReadUvarint(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Prefix{}, err
|
return netip.Prefix{}, err
|
||||||
}
|
}
|
||||||
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
|
addrSlice := make([]byte, addrLen)
|
||||||
|
_, err = io.ReadFull(reader, addrSlice)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Prefix{}, err
|
||||||
|
}
|
||||||
|
prefixBits, err := reader.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Prefix{}, err
|
return netip.Prefix{}, err
|
||||||
}
|
}
|
||||||
@@ -21,11 +27,16 @@ func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
||||||
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
addrSlice := prefix.Addr().AsSlice()
|
||||||
|
_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
_, err = writer.Write(addrSlice)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(uint8(prefix.Bits()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package srs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
@@ -22,11 +22,6 @@ type myIPRange struct {
|
|||||||
to netip.Addr
|
to netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
type myIPRangeData struct {
|
|
||||||
From []byte
|
|
||||||
To []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
||||||
version, err := reader.ReadByte()
|
version, err := reader.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,17 +36,30 @@ func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ranges := make([]myIPRangeData, length)
|
|
||||||
err = varbin.Read(reader, binary.BigEndian, &ranges)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mySet := &myIPSet{
|
mySet := &myIPSet{
|
||||||
rr: make([]myIPRange, len(ranges)),
|
rr: make([]myIPRange, length),
|
||||||
}
|
}
|
||||||
for i, rangeData := range ranges {
|
for i := range mySet.rr {
|
||||||
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
|
fromLen, err := binary.ReadUvarint(reader)
|
||||||
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fromBytes := make([]byte, fromLen)
|
||||||
|
_, err = io.ReadFull(reader, fromBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
toLen, err := binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
toBytes := make([]byte, toLen)
|
||||||
|
_, err = io.ReadFull(reader, toBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mySet.rr[i].from = M.AddrFromIP(fromBytes)
|
||||||
|
mySet.rr[i].to = M.AddrFromIP(toBytes)
|
||||||
}
|
}
|
||||||
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
||||||
}
|
}
|
||||||
@@ -61,18 +69,27 @@ func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData {
|
mySet := (*myIPSet)(unsafe.Pointer(set))
|
||||||
return myIPRangeData{
|
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
|
||||||
From: rr.from.AsSlice(),
|
|
||||||
To: rr.to.AsSlice(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
err = binary.Write(writer, binary.BigEndian, uint64(len(dataList)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, data := range dataList {
|
for _, rr := range mySet.rr {
|
||||||
err = varbin.Write(writer, binary.BigEndian, data)
|
fromBytes := rr.from.AsSlice()
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(fromBytes)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(fromBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
toBytes := rr.to.AsSlice()
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(toBytes)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(toBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/libdns/acmedns"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
"github.com/libdns/cloudflare"
|
"github.com/libdns/cloudflare"
|
||||||
"github.com/mholt/acmez/v3/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
@@ -114,13 +115,24 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
switch dnsOptions.Provider {
|
switch dnsOptions.Provider {
|
||||||
case C.DNSProviderAliDNS:
|
case C.DNSProviderAliDNS:
|
||||||
solver.DNSProvider = &alidns.Provider{
|
solver.DNSProvider = &alidns.Provider{
|
||||||
AccKeyID: dnsOptions.AliDNSOptions.AccessKeyID,
|
CredentialInfo: alidns.CredentialInfo{
|
||||||
AccKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret,
|
AccessKeyID: dnsOptions.AliDNSOptions.AccessKeyID,
|
||||||
RegionID: dnsOptions.AliDNSOptions.RegionID,
|
AccessKeySecret: dnsOptions.AliDNSOptions.AccessKeySecret,
|
||||||
|
RegionID: dnsOptions.AliDNSOptions.RegionID,
|
||||||
|
SecurityToken: dnsOptions.AliDNSOptions.SecurityToken,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
case C.DNSProviderCloudflare:
|
case C.DNSProviderCloudflare:
|
||||||
solver.DNSProvider = &cloudflare.Provider{
|
solver.DNSProvider = &cloudflare.Provider{
|
||||||
APIToken: dnsOptions.CloudflareOptions.APIToken,
|
APIToken: dnsOptions.CloudflareOptions.APIToken,
|
||||||
|
ZoneToken: dnsOptions.CloudflareOptions.ZoneToken,
|
||||||
|
}
|
||||||
|
case C.DNSProviderACMEDNS:
|
||||||
|
solver.DNSProvider = &acmedns.Provider{
|
||||||
|
Username: dnsOptions.ACMEDNSOptions.Username,
|
||||||
|
Password: dnsOptions.ACMEDNSOptions.Password,
|
||||||
|
Subdomain: dnsOptions.ACMEDNSOptions.Subdomain,
|
||||||
|
ServerURL: dnsOptions.ACMEDNSOptions.ServerURL,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
|
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
@@ -38,7 +37,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
}
|
}
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
||||||
deprecated.Report(ctx, deprecated.OptionLegacyECHOptions)
|
return nil, E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0")
|
||||||
}
|
}
|
||||||
if len(echConfig) > 0 {
|
if len(echConfig) > 0 {
|
||||||
block, rest := pem.Decode(echConfig)
|
block, rest := pem.Decode(echConfig)
|
||||||
@@ -51,6 +50,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
return &ECHClientConfig{
|
return &ECHClientConfig{
|
||||||
ECHCapableConfig: clientConfig,
|
ECHCapableConfig: clientConfig,
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
|
queryServerName: options.ECH.QueryServerName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
||||||
deprecated.Report(ctx, deprecated.OptionLegacyECHOptions)
|
return E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -108,10 +108,11 @@ func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
|
|||||||
|
|
||||||
type ECHClientConfig struct {
|
type ECHClientConfig struct {
|
||||||
ECHCapableConfig
|
ECHCapableConfig
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
lastTTL time.Duration
|
queryServerName string
|
||||||
lastUpdate time.Time
|
lastTTL time.Duration
|
||||||
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
@@ -130,13 +131,17 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
defer s.access.Unlock()
|
||||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
|
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
|
||||||
|
queryServerName := s.queryServerName
|
||||||
|
if queryServerName == "" {
|
||||||
|
queryServerName = s.ServerName()
|
||||||
|
}
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []mDNS.Question{
|
Question: []mDNS.Question{
|
||||||
{
|
{
|
||||||
Name: mDNS.Fqdn(s.ServerName()),
|
Name: mDNS.Fqdn(queryServerName),
|
||||||
Qtype: mDNS.TypeHTTPS,
|
Qtype: mDNS.TypeHTTPS,
|
||||||
Qclass: mDNS.ClassINET,
|
Qclass: mDNS.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -175,7 +180,12 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) Clone() Config {
|
func (s *ECHClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &ECHClientConfig{
|
||||||
|
ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig),
|
||||||
|
dnsRouter: s.dnsRouter,
|
||||||
|
queryServerName: s.queryServerName,
|
||||||
|
lastUpdate: s.lastUpdate,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
||||||
@@ -21,7 +22,7 @@ var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
|||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*adapter.URLTestHistory
|
delayHistory map[string]*adapter.URLTestHistory
|
||||||
updateHook chan<- struct{}
|
updateHook *observable.Subscriber[struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
@@ -30,7 +31,7 @@ func NewHistoryStorage() *HistoryStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
func (s *HistoryStorage) SetHook(hook *observable.Subscriber[struct{}]) {
|
||||||
s.updateHook = hook
|
s.updateHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +61,7 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTes
|
|||||||
func (s *HistoryStorage) notifyUpdated() {
|
func (s *HistoryStorage) notifyUpdated() {
|
||||||
updateHook := s.updateHook
|
updateHook := s.updateHook
|
||||||
if updateHook != nil {
|
if updateHook != nil {
|
||||||
select {
|
updateHook.Emit(struct{}{})
|
||||||
case updateHook <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ package constant
|
|||||||
const (
|
const (
|
||||||
CertificateStoreSystem = "system"
|
CertificateStoreSystem = "system"
|
||||||
CertificateStoreMozilla = "mozilla"
|
CertificateStoreMozilla = "mozilla"
|
||||||
|
CertificateStoreChrome = "chrome"
|
||||||
CertificateStoreNone = "none"
|
CertificateStoreNone = "none"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import "time"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DHCPTTL = time.Hour
|
DHCPTTL = time.Hour
|
||||||
DHCPTimeout = time.Minute
|
DHCPTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,4 +33,5 @@ const (
|
|||||||
const (
|
const (
|
||||||
DNSProviderAliDNS = "alidns"
|
DNSProviderAliDNS = "alidns"
|
||||||
DNSProviderCloudflare = "cloudflare"
|
DNSProviderCloudflare = "cloudflare"
|
||||||
|
DNSProviderACMEDNS = "acmedns"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const (
|
|||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
TypeCCM = "ccm"
|
TypeCCM = "ccm"
|
||||||
|
TypeOCM = "ocm"
|
||||||
|
TypeOOMKiller = "oom-killer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -84,6 +86,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
|
case TypeTailscale:
|
||||||
|
return "Tailscale"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const (
|
|||||||
RuleActionTypeRoute = "route"
|
RuleActionTypeRoute = "route"
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
RuleActionTypeRouteOptions = "route-options"
|
||||||
RuleActionTypeDirect = "direct"
|
RuleActionTypeDirect = "direct"
|
||||||
|
RuleActionTypeBypass = "bypass"
|
||||||
RuleActionTypeReject = "reject"
|
RuleActionTypeReject = "reject"
|
||||||
RuleActionTypeHijackDNS = "hijack-dns"
|
RuleActionTypeHijackDNS = "hijack-dns"
|
||||||
RuleActionTypeSniff = "sniff"
|
RuleActionTypeSniff = "sniff"
|
||||||
|
|||||||
29
daemon/deprecated.go
Normal file
29
daemon/deprecated.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
||||||
|
|
||||||
|
type deprecatedManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
notes []deprecated.Note
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.notes = common.Uniq(append(m.notes, feature))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *deprecatedManager) Get() []deprecated.Note {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
notes := m.notes
|
||||||
|
m.notes = nil
|
||||||
|
return notes
|
||||||
|
}
|
||||||
148
daemon/instance.go
Normal file
148
daemon/instance.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
|
"github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instance struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
instance *box.Box
|
||||||
|
connectionManager adapter.ConnectionManager
|
||||||
|
clashServer adapter.ClashServer
|
||||||
|
cacheFile adapter.CacheFile
|
||||||
|
pauseManager pause.Manager
|
||||||
|
urlTestHistoryStorage *urltest.HistoryStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StartedService) CheckConfig(configContent string) error {
|
||||||
|
options, err := parseConfig(s.ctx, configContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(s.ctx)
|
||||||
|
defer cancel()
|
||||||
|
instance, err := box.New(box.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
instance.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StartedService) FormatConfig(configContent string) (string, error) {
|
||||||
|
options, err := parseConfig(s.ctx, configContent)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
encoder := json.NewEncoder(&buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type OverrideOptions struct {
|
||||||
|
AutoRedirect bool
|
||||||
|
IncludePackage []string
|
||||||
|
ExcludePackage []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StartedService) newInstance(profileContent string, overrideOptions *OverrideOptions) (*Instance, error) {
|
||||||
|
ctx := s.ctx
|
||||||
|
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
|
||||||
|
ctx, cancel := context.WithCancel(include.Context(ctx))
|
||||||
|
options, err := parseConfig(ctx, profileContent)
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if overrideOptions != nil {
|
||||||
|
for _, inbound := range options.Inbounds {
|
||||||
|
if tunInboundOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN {
|
||||||
|
tunInboundOptions.AutoRedirect = overrideOptions.AutoRedirect
|
||||||
|
tunInboundOptions.IncludePackage = append(tunInboundOptions.IncludePackage, overrideOptions.IncludePackage...)
|
||||||
|
tunInboundOptions.ExcludePackage = append(tunInboundOptions.ExcludePackage, overrideOptions.ExcludePackage...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.oomKiller && C.IsIos {
|
||||||
|
if !common.Any(options.Services, func(it option.Service) bool {
|
||||||
|
return it.Type == C.TypeOOMKiller
|
||||||
|
}) {
|
||||||
|
options.Services = append(options.Services, option.Service{
|
||||||
|
Type: C.TypeOOMKiller,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
|
i := &Instance{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
urlTestHistoryStorage: urlTestHistoryStorage,
|
||||||
|
}
|
||||||
|
boxInstance, err := box.New(box.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options,
|
||||||
|
PlatformLogWriter: s,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i.instance = boxInstance
|
||||||
|
i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx)
|
||||||
|
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
||||||
|
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||||
|
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
||||||
|
log.SetStdLogger(boxInstance.LogFactory().Logger())
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Start() error {
|
||||||
|
return i.instance.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Close() error {
|
||||||
|
i.cancel()
|
||||||
|
i.urlTestHistoryStorage.Close()
|
||||||
|
return i.instance.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Box() *box.Box {
|
||||||
|
return i.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) PauseManager() pause.Manager {
|
||||||
|
return i.pauseManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
|
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, E.Cause(err, "decode config")
|
||||||
|
}
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
9
daemon/platform.go
Normal file
9
daemon/platform.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
type PlatformHandler interface {
|
||||||
|
ServiceStop() error
|
||||||
|
ServiceReload() error
|
||||||
|
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||||
|
SetSystemProxyEnabled(enabled bool) error
|
||||||
|
WriteDebugMessage(message string)
|
||||||
|
}
|
||||||
1056
daemon/started_service.go
Normal file
1056
daemon/started_service.go
Normal file
File diff suppressed because it is too large
Load Diff
2072
daemon/started_service.pb.go
Normal file
2072
daemon/started_service.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
217
daemon/started_service.proto
Normal file
217
daemon/started_service.proto
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package daemon;
|
||||||
|
option go_package = "github.com/sagernet/sing-box/daemon";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
service StartedService {
|
||||||
|
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
|
rpc ReloadService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
|
|
||||||
|
rpc SubscribeServiceStatus(google.protobuf.Empty) returns(stream ServiceStatus) {}
|
||||||
|
rpc SubscribeLog(google.protobuf.Empty) returns(stream Log) {}
|
||||||
|
rpc GetDefaultLogLevel(google.protobuf.Empty) returns(DefaultLogLevel) {}
|
||||||
|
rpc ClearLogs(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||||
|
rpc SubscribeStatus(SubscribeStatusRequest) returns(stream Status) {}
|
||||||
|
rpc SubscribeGroups(google.protobuf.Empty) returns(stream Groups) {}
|
||||||
|
|
||||||
|
rpc GetClashModeStatus(google.protobuf.Empty) returns(ClashModeStatus) {}
|
||||||
|
rpc SubscribeClashMode(google.protobuf.Empty) returns(stream ClashMode) {}
|
||||||
|
rpc SetClashMode(ClashMode) returns(google.protobuf.Empty) {}
|
||||||
|
|
||||||
|
rpc URLTest(URLTestRequest) returns(google.protobuf.Empty) {}
|
||||||
|
rpc SelectOutbound(SelectOutboundRequest) returns (google.protobuf.Empty) {}
|
||||||
|
rpc SetGroupExpand(SetGroupExpandRequest) returns (google.protobuf.Empty) {}
|
||||||
|
|
||||||
|
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||||
|
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||||
|
|
||||||
|
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
||||||
|
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||||
|
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||||
|
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||||
|
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServiceStatus {
|
||||||
|
enum Type {
|
||||||
|
IDLE = 0;
|
||||||
|
STARTING = 1;
|
||||||
|
STARTED = 2;
|
||||||
|
STOPPING = 3;
|
||||||
|
FATAL = 4;
|
||||||
|
}
|
||||||
|
Type status = 1;
|
||||||
|
string errorMessage = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReloadServiceRequest {
|
||||||
|
string newProfileContent = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubscribeStatusRequest {
|
||||||
|
int64 interval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogLevel {
|
||||||
|
PANIC = 0;
|
||||||
|
FATAL = 1;
|
||||||
|
ERROR = 2;
|
||||||
|
WARN = 3;
|
||||||
|
INFO = 4;
|
||||||
|
DEBUG = 5;
|
||||||
|
TRACE = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Log {
|
||||||
|
repeated Message messages = 1;
|
||||||
|
bool reset = 2;
|
||||||
|
message Message {
|
||||||
|
LogLevel level = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DefaultLogLevel {
|
||||||
|
LogLevel level = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Status {
|
||||||
|
uint64 memory = 1;
|
||||||
|
int32 goroutines = 2;
|
||||||
|
int32 connectionsIn = 3;
|
||||||
|
int32 connectionsOut = 4;
|
||||||
|
bool trafficAvailable = 5;
|
||||||
|
int64 uplink = 6;
|
||||||
|
int64 downlink = 7;
|
||||||
|
int64 uplinkTotal = 8;
|
||||||
|
int64 downlinkTotal = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Groups {
|
||||||
|
repeated Group group = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Group {
|
||||||
|
string tag = 1;
|
||||||
|
string type = 2;
|
||||||
|
bool selectable = 3;
|
||||||
|
string selected = 4;
|
||||||
|
bool isExpand = 5;
|
||||||
|
repeated GroupItem items = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GroupItem {
|
||||||
|
string tag = 1;
|
||||||
|
string type = 2;
|
||||||
|
int64 urlTestTime = 3;
|
||||||
|
int32 urlTestDelay = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message URLTestRequest {
|
||||||
|
string outboundTag = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SelectOutboundRequest {
|
||||||
|
string groupTag = 1;
|
||||||
|
string outboundTag = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetGroupExpandRequest {
|
||||||
|
string groupTag = 1;
|
||||||
|
bool isExpand = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClashMode {
|
||||||
|
string mode = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClashModeStatus {
|
||||||
|
repeated string modeList = 1;
|
||||||
|
string currentMode = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SystemProxyStatus {
|
||||||
|
bool available = 1;
|
||||||
|
bool enabled = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetSystemProxyEnabledRequest {
|
||||||
|
bool enabled = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubscribeConnectionsRequest {
|
||||||
|
int64 interval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConnectionEventType {
|
||||||
|
CONNECTION_EVENT_NEW = 0;
|
||||||
|
CONNECTION_EVENT_UPDATE = 1;
|
||||||
|
CONNECTION_EVENT_CLOSED = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionEvent {
|
||||||
|
ConnectionEventType type = 1;
|
||||||
|
string id = 2;
|
||||||
|
Connection connection = 3;
|
||||||
|
int64 uplinkDelta = 4;
|
||||||
|
int64 downlinkDelta = 5;
|
||||||
|
int64 closedAt = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionEvents {
|
||||||
|
repeated ConnectionEvent events = 1;
|
||||||
|
bool reset = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Connection {
|
||||||
|
string id = 1;
|
||||||
|
string inbound = 2;
|
||||||
|
string inboundType = 3;
|
||||||
|
int32 ipVersion = 4;
|
||||||
|
string network = 5;
|
||||||
|
string source = 6;
|
||||||
|
string destination = 7;
|
||||||
|
string domain = 8;
|
||||||
|
string protocol = 9;
|
||||||
|
string user = 10;
|
||||||
|
string fromOutbound = 11;
|
||||||
|
int64 createdAt = 12;
|
||||||
|
int64 closedAt = 13;
|
||||||
|
int64 uplink = 14;
|
||||||
|
int64 downlink = 15;
|
||||||
|
int64 uplinkTotal = 16;
|
||||||
|
int64 downlinkTotal = 17;
|
||||||
|
string rule = 18;
|
||||||
|
string outbound = 19;
|
||||||
|
string outboundType = 20;
|
||||||
|
repeated string chainList = 21;
|
||||||
|
ProcessInfo processInfo = 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProcessInfo {
|
||||||
|
uint32 processId = 1;
|
||||||
|
int32 userId = 2;
|
||||||
|
string userName = 3;
|
||||||
|
string processPath = 4;
|
||||||
|
string packageName = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CloseConnectionRequest {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeprecatedWarnings {
|
||||||
|
repeated DeprecatedWarning warnings = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeprecatedWarning {
|
||||||
|
string message = 1;
|
||||||
|
bool impending = 2;
|
||||||
|
string migrationLink = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartedAt {
|
||||||
|
int64 startedAt = 1;
|
||||||
|
}
|
||||||
916
daemon/started_service_grpc.pb.go
Normal file
916
daemon/started_service_grpc.pb.go
Normal file
@@ -0,0 +1,916 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
||||||
|
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
||||||
|
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||||
|
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||||
|
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||||
|
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||||
|
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||||
|
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||||
|
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||||
|
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||||
|
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||||
|
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||||
|
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||||
|
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||||
|
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||||
|
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||||
|
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||||
|
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||||
|
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||||
|
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||||
|
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartedServiceClient is the client API for StartedService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type StartedServiceClient interface {
|
||||||
|
StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error)
|
||||||
|
SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error)
|
||||||
|
GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error)
|
||||||
|
ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error)
|
||||||
|
SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error)
|
||||||
|
GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error)
|
||||||
|
SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error)
|
||||||
|
SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||||
|
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
||||||
|
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
|
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
||||||
|
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type startedServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStartedServiceClient(cc grpc.ClientConnInterface) StartedServiceClient {
|
||||||
|
return &startedServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) StopService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_StopService_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) ReloadService(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_ReloadService_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SubscribeServiceStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ServiceStatus], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[0], StartedService_SubscribeServiceStatus_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[emptypb.Empty, ServiceStatus]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeServiceStatusClient = grpc.ServerStreamingClient[ServiceStatus]
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SubscribeLog(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Log], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[1], StartedService_SubscribeLog_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[emptypb.Empty, Log]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeLogClient = grpc.ServerStreamingClient[Log]
|
||||||
|
|
||||||
|
func (c *startedServiceClient) GetDefaultLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DefaultLogLevel, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(DefaultLogLevel)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_GetDefaultLogLevel_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) ClearLogs(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_ClearLogs_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SubscribeStatus(ctx context.Context, in *SubscribeStatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Status], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[2], StartedService_SubscribeStatus_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[SubscribeStatusRequest, Status]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeStatusClient = grpc.ServerStreamingClient[Status]
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SubscribeGroups(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Groups], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[3], StartedService_SubscribeGroups_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[emptypb.Empty, Groups]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeGroupsClient = grpc.ServerStreamingClient[Groups]
|
||||||
|
|
||||||
|
func (c *startedServiceClient) GetClashModeStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClashModeStatus, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ClashModeStatus)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_GetClashModeStatus_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SubscribeClashMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClashMode], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[4], StartedService_SubscribeClashMode_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[emptypb.Empty, ClashMode]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeClashModeClient = grpc.ServerStreamingClient[ClashMode]
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SetClashMode(ctx context.Context, in *ClashMode, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_SetClashMode_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) URLTest(ctx context.Context, in *URLTestRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_URLTest_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SelectOutbound(ctx context.Context, in *SelectOutboundRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_SelectOutbound_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_SetGroupExpand_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(SystemProxyStatus)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_GetSystemProxyStatus_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_SetSystemProxyEnabled_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, ConnectionEvents]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[ConnectionEvents]
|
||||||
|
|
||||||
|
func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_CloseConnection_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_CloseAllConnections_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(DeprecatedWarnings)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_GetDeprecatedWarnings_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(StartedAt)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_GetStartedAt_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedServiceServer is the server API for StartedService service.
|
||||||
|
// All implementations must embed UnimplementedStartedServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type StartedServiceServer interface {
|
||||||
|
StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
|
ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
|
SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error
|
||||||
|
SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error
|
||||||
|
GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error)
|
||||||
|
ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
|
SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error
|
||||||
|
SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error
|
||||||
|
GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error)
|
||||||
|
SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error
|
||||||
|
SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error)
|
||||||
|
URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error)
|
||||||
|
SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error)
|
||||||
|
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||||
|
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||||
|
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||||
|
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
||||||
|
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||||
|
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
|
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
||||||
|
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
||||||
|
mustEmbedUnimplementedStartedServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedStartedServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedStartedServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method StopService not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ReloadService not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method SubscribeServiceStatus not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method SubscribeLog not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetDefaultLogLevel not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ClearLogs not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method SubscribeStatus not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method SubscribeGroups not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetClashModeStatus not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method SubscribeClashMode not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SetClashMode not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method URLTest not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SelectOutbound not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SetGroupExpand not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetSystemProxyStatus not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method CloseConnection not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method CloseAllConnections not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||||
|
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeStartedServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to StartedServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeStartedServiceServer interface {
|
||||||
|
mustEmbedUnimplementedStartedServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedStartedServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&StartedService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_StopService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).StopService(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_StopService_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).StopService(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_ReloadService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).ReloadService(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_ReloadService_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).ReloadService(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SubscribeServiceStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(emptypb.Empty)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(StartedServiceServer).SubscribeServiceStatus(m, &grpc.GenericServerStream[emptypb.Empty, ServiceStatus]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeServiceStatusServer = grpc.ServerStreamingServer[ServiceStatus]
|
||||||
|
|
||||||
|
func _StartedService_SubscribeLog_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(emptypb.Empty)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(StartedServiceServer).SubscribeLog(m, &grpc.GenericServerStream[emptypb.Empty, Log]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeLogServer = grpc.ServerStreamingServer[Log]
|
||||||
|
|
||||||
|
func _StartedService_GetDefaultLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_GetDefaultLogLevel_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).GetDefaultLogLevel(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_ClearLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).ClearLogs(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_ClearLogs_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).ClearLogs(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SubscribeStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(SubscribeStatusRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(StartedServiceServer).SubscribeStatus(m, &grpc.GenericServerStream[SubscribeStatusRequest, Status]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeStatusServer = grpc.ServerStreamingServer[Status]
|
||||||
|
|
||||||
|
func _StartedService_SubscribeGroups_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(emptypb.Empty)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(StartedServiceServer).SubscribeGroups(m, &grpc.GenericServerStream[emptypb.Empty, Groups]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeGroupsServer = grpc.ServerStreamingServer[Groups]
|
||||||
|
|
||||||
|
func _StartedService_GetClashModeStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).GetClashModeStatus(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_GetClashModeStatus_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).GetClashModeStatus(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SubscribeClashMode_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(emptypb.Empty)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(StartedServiceServer).SubscribeClashMode(m, &grpc.GenericServerStream[emptypb.Empty, ClashMode]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeClashModeServer = grpc.ServerStreamingServer[ClashMode]
|
||||||
|
|
||||||
|
func _StartedService_SetClashMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ClashMode)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).SetClashMode(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_SetClashMode_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).SetClashMode(ctx, req.(*ClashMode))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_URLTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(URLTestRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).URLTest(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_URLTest_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).URLTest(ctx, req.(*URLTestRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SelectOutbound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SelectOutboundRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).SelectOutbound(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_SelectOutbound_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).SelectOutbound(ctx, req.(*SelectOutboundRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SetGroupExpand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetGroupExpandRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).SetGroupExpand(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_SetGroupExpand_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).SetGroupExpand(ctx, req.(*SetGroupExpandRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_GetSystemProxyStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_GetSystemProxyStatus_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).GetSystemProxyStatus(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(SetSystemProxyEnabledRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_SetSystemProxyEnabled_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).SetSystemProxyEnabled(ctx, req.(*SetSystemProxyEnabledRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(SubscribeConnectionsRequest)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, ConnectionEvents]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[ConnectionEvents]
|
||||||
|
|
||||||
|
func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CloseConnectionRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).CloseConnection(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_CloseConnection_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).CloseConnection(ctx, req.(*CloseConnectionRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_CloseAllConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).CloseAllConnections(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_CloseAllConnections_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).CloseAllConnections(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_GetDeprecatedWarnings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_GetDeprecatedWarnings_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).GetDeprecatedWarnings(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(emptypb.Empty)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(StartedServiceServer).GetStartedAt(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: StartedService_GetStartedAt_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(StartedServiceServer).GetStartedAt(ctx, req.(*emptypb.Empty))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "daemon.StartedService",
|
||||||
|
HandlerType: (*StartedServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "StopService",
|
||||||
|
Handler: _StartedService_StopService_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ReloadService",
|
||||||
|
Handler: _StartedService_ReloadService_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetDefaultLogLevel",
|
||||||
|
Handler: _StartedService_GetDefaultLogLevel_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ClearLogs",
|
||||||
|
Handler: _StartedService_ClearLogs_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetClashModeStatus",
|
||||||
|
Handler: _StartedService_GetClashModeStatus_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetClashMode",
|
||||||
|
Handler: _StartedService_SetClashMode_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "URLTest",
|
||||||
|
Handler: _StartedService_URLTest_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SelectOutbound",
|
||||||
|
Handler: _StartedService_SelectOutbound_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetGroupExpand",
|
||||||
|
Handler: _StartedService_SetGroupExpand_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetSystemProxyStatus",
|
||||||
|
Handler: _StartedService_GetSystemProxyStatus_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetSystemProxyEnabled",
|
||||||
|
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CloseConnection",
|
||||||
|
Handler: _StartedService_CloseConnection_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CloseAllConnections",
|
||||||
|
Handler: _StartedService_CloseAllConnections_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetDeprecatedWarnings",
|
||||||
|
Handler: _StartedService_GetDeprecatedWarnings_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetStartedAt",
|
||||||
|
Handler: _StartedService_GetStartedAt_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeServiceStatus",
|
||||||
|
Handler: _StartedService_SubscribeServiceStatus_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeLog",
|
||||||
|
Handler: _StartedService_SubscribeLog_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeStatus",
|
||||||
|
Handler: _StartedService_SubscribeStatus_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeGroups",
|
||||||
|
Handler: _StartedService_SubscribeGroups_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeClashMode",
|
||||||
|
Handler: _StartedService_SubscribeClashMode_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "SubscribeConnections",
|
||||||
|
Handler: _StartedService_SubscribeConnections_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Metadata: "daemon/started_service.proto",
|
||||||
|
}
|
||||||
8
debug.go
8
debug.go
@@ -3,11 +3,11 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
func applyDebugOptions(options option.DebugOptions) error {
|
||||||
applyDebugListenOption(options)
|
applyDebugListenOption(options)
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
@@ -26,9 +26,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
|||||||
}
|
}
|
||||||
if options.MemoryLimit.Value() != 0 {
|
if options.MemoryLimit.Value() != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||||
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
conntrack.KillerEnabled = *options.OOMKiller
|
return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead")
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if c.cache != nil {
|
if c.cache != nil {
|
||||||
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
||||||
if loaded {
|
if loaded {
|
||||||
<-cond
|
select {
|
||||||
|
case <-cond:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
c.cacheLock.Delete(question)
|
c.cacheLock.Delete(question)
|
||||||
@@ -154,7 +158,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
} else if c.transportCache != nil {
|
} else if c.transportCache != nil {
|
||||||
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
|
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
|
||||||
if loaded {
|
if loaded {
|
||||||
<-cond
|
select {
|
||||||
|
case <-cond:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
c.transportCacheLock.Delete(question)
|
c.transportCacheLock.Delete(question)
|
||||||
@@ -232,8 +240,10 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
// TODO: add accept_any rule and support to check response instead of addresses
|
// TODO: add accept_any rule and support to check response instead of addresses
|
||||||
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
|
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||||
rejected = true
|
rejected = true
|
||||||
|
} else if len(response.Answer) == 0 {
|
||||||
|
rejected = !responseChecker(nil)
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(MessageToAddresses(response))
|
rejected = !responseChecker(MessageToAddresses(response))
|
||||||
}
|
}
|
||||||
@@ -314,16 +324,20 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
} else {
|
} else {
|
||||||
strategy = options.Strategy
|
strategy = options.Strategy
|
||||||
}
|
}
|
||||||
|
lookupOptions := options
|
||||||
|
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||||
|
lookupOptions.Strategy = strategy
|
||||||
|
}
|
||||||
if strategy == C.DomainStrategyIPv4Only {
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)
|
||||||
}
|
}
|
||||||
var response4 []netip.Addr
|
var response4 []netip.Addr
|
||||||
var response6 []netip.Addr
|
var response6 []netip.Addr
|
||||||
var group task.Group
|
var group task.Group
|
||||||
group.Append("exchange4", func(ctx context.Context) error {
|
group.Append("exchange4", func(ctx context.Context) error {
|
||||||
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -331,7 +345,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
group.Append("exchange6", func(ctx context.Context) error {
|
group.Append("exchange6", func(ctx context.Context) error {
|
||||||
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -353,68 +367,6 @@ func (c *Client) ClearCache() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool) {
|
|
||||||
if c.disableCache || c.independentCache {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
if dns.IsFqdn(domain) {
|
|
||||||
domain = domain[:len(domain)-1]
|
|
||||||
}
|
|
||||||
dnsName := dns.Fqdn(domain)
|
|
||||||
if strategy == C.DomainStrategyIPv4Only {
|
|
||||||
addresses, err := c.questionCache(dns.Question{
|
|
||||||
Name: dnsName,
|
|
||||||
Qtype: dns.TypeA,
|
|
||||||
Qclass: dns.ClassINET,
|
|
||||||
}, nil)
|
|
||||||
if err != ErrNotCached {
|
|
||||||
return addresses, true
|
|
||||||
}
|
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
|
||||||
addresses, err := c.questionCache(dns.Question{
|
|
||||||
Name: dnsName,
|
|
||||||
Qtype: dns.TypeAAAA,
|
|
||||||
Qclass: dns.ClassINET,
|
|
||||||
}, nil)
|
|
||||||
if err != ErrNotCached {
|
|
||||||
return addresses, true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
response4, _ := c.loadResponse(dns.Question{
|
|
||||||
Name: dnsName,
|
|
||||||
Qtype: dns.TypeA,
|
|
||||||
Qclass: dns.ClassINET,
|
|
||||||
}, nil)
|
|
||||||
if response4 == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
response6, _ := c.loadResponse(dns.Question{
|
|
||||||
Name: dnsName,
|
|
||||||
Qtype: dns.TypeAAAA,
|
|
||||||
Qclass: dns.ClassINET,
|
|
||||||
}, nil)
|
|
||||||
if response6 == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool) {
|
|
||||||
if c.disableCache || c.independentCache || len(message.Question) != 1 {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
question := message.Question[0]
|
|
||||||
response, ttl := c.loadResponse(question, nil)
|
|
||||||
if response == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
logCachedResponse(c.logger, ctx, response, ttl)
|
|
||||||
response.Id = message.Id
|
|
||||||
return response, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr {
|
func sortAddresses(response4 []netip.Addr, response6 []netip.Addr, strategy C.DomainStrategy) []netip.Addr {
|
||||||
if strategy == C.DomainStrategyPreferIPv6 {
|
if strategy == C.DomainStrategyPreferIPv6 {
|
||||||
return append(response6, response4...)
|
return append(response6, response4...)
|
||||||
|
|||||||
208
dns/router.go
208
dns/router.go
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
R "github.com/sagernet/sing-box/route/rule"
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
@@ -38,7 +37,7 @@ type Router struct {
|
|||||||
rules []adapter.DNSRule
|
rules []adapter.DNSRule
|
||||||
defaultDomainStrategy C.DomainStrategy
|
defaultDomainStrategy C.DomainStrategy
|
||||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||||
platformInterface platform.Interface
|
platformInterface adapter.PlatformInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||||
@@ -196,7 +195,16 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r.transport.Default(), nil, -1
|
transport := r.transport.Default()
|
||||||
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = legacyTransport.LegacyStrategy()
|
||||||
|
}
|
||||||
|
if !options.ClientSubnet.IsValid() {
|
||||||
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transport, nil, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
||||||
@@ -214,97 +222,89 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
}
|
}
|
||||||
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
||||||
var (
|
var (
|
||||||
|
response *mDNS.Msg
|
||||||
transport adapter.DNSTransport
|
transport adapter.DNSTransport
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
response, cached := r.client.ExchangeCache(ctx, message)
|
var metadata *adapter.InboundContext
|
||||||
if !cached {
|
ctx, metadata = adapter.ExtendContext(ctx)
|
||||||
var metadata *adapter.InboundContext
|
metadata.Destination = M.Socksaddr{}
|
||||||
ctx, metadata = adapter.ExtendContext(ctx)
|
metadata.QueryType = message.Question[0].Qtype
|
||||||
metadata.Destination = M.Socksaddr{}
|
switch metadata.QueryType {
|
||||||
metadata.QueryType = message.Question[0].Qtype
|
case mDNS.TypeA:
|
||||||
switch metadata.QueryType {
|
metadata.IPVersion = 4
|
||||||
case mDNS.TypeA:
|
case mDNS.TypeAAAA:
|
||||||
metadata.IPVersion = 4
|
metadata.IPVersion = 6
|
||||||
case mDNS.TypeAAAA:
|
}
|
||||||
metadata.IPVersion = 6
|
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||||
}
|
if options.Transport != nil {
|
||||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
transport = options.Transport
|
||||||
if options.Transport != nil {
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
transport = options.Transport
|
|
||||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
|
||||||
if options.Strategy == C.DomainStrategyAsIS {
|
|
||||||
options.Strategy = legacyTransport.LegacyStrategy()
|
|
||||||
}
|
|
||||||
if !options.ClientSubnet.IsValid() {
|
|
||||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.Strategy == C.DomainStrategyAsIS {
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
options.Strategy = r.defaultDomainStrategy
|
options.Strategy = legacyTransport.LegacyStrategy()
|
||||||
}
|
}
|
||||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
if !options.ClientSubnet.IsValid() {
|
||||||
} else {
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
var (
|
|
||||||
rule adapter.DNSRule
|
|
||||||
ruleIndex int
|
|
||||||
)
|
|
||||||
ruleIndex = -1
|
|
||||||
for {
|
|
||||||
dnsCtx := adapter.OverrideContext(ctx)
|
|
||||||
dnsOptions := options
|
|
||||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
|
||||||
if rule != nil {
|
|
||||||
switch action := rule.Action().(type) {
|
|
||||||
case *R.RuleActionReject:
|
|
||||||
switch action.Method {
|
|
||||||
case C.RuleActionRejectMethodDefault:
|
|
||||||
return &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeRefused,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{message.Question[0]},
|
|
||||||
}, nil
|
|
||||||
case C.RuleActionRejectMethodDrop:
|
|
||||||
return nil, tun.ErrDrop
|
|
||||||
}
|
|
||||||
case *R.RuleActionPredefined:
|
|
||||||
return action.Response(message), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
|
||||||
if rule != nil && rule.WithAddressLimit() {
|
|
||||||
responseCheck = func(responseAddrs []netip.Addr) bool {
|
|
||||||
metadata.DestinationAddresses = responseAddrs
|
|
||||||
return rule.MatchAddressLimit(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
|
||||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
|
||||||
}
|
|
||||||
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
|
||||||
var rejected bool
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, ErrResponseRejectedCached) {
|
|
||||||
rejected = true
|
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())), " (cached)")
|
|
||||||
} else if errors.Is(err, ErrResponseRejected) {
|
|
||||||
rejected = true
|
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
|
||||||
} else if len(message.Question) > 0 {
|
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
|
||||||
} else {
|
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if responseCheck != nil && rejected {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
|
options.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||||
|
} else {
|
||||||
|
var (
|
||||||
|
rule adapter.DNSRule
|
||||||
|
ruleIndex int
|
||||||
|
)
|
||||||
|
ruleIndex = -1
|
||||||
|
for {
|
||||||
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
|
dnsOptions := options
|
||||||
|
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||||
|
if rule != nil {
|
||||||
|
switch action := rule.Action().(type) {
|
||||||
|
case *R.RuleActionReject:
|
||||||
|
switch action.Method {
|
||||||
|
case C.RuleActionRejectMethodDefault:
|
||||||
|
return &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Rcode: mDNS.RcodeRefused,
|
||||||
|
Response: true,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{message.Question[0]},
|
||||||
|
}, nil
|
||||||
|
case C.RuleActionRejectMethodDrop:
|
||||||
|
return nil, tun.ErrDrop
|
||||||
|
}
|
||||||
|
case *R.RuleActionPredefined:
|
||||||
|
return action.Response(message), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCheck := addressLimitResponseCheck(rule, metadata)
|
||||||
|
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||||
|
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||||
|
}
|
||||||
|
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
||||||
|
var rejected bool
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
|
rejected = true
|
||||||
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())), " (cached)")
|
||||||
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
|
rejected = true
|
||||||
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
} else if len(message.Question) > 0 {
|
||||||
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||||
|
} else {
|
||||||
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if responseCheck != nil && rejected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -327,7 +327,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||||
var (
|
var (
|
||||||
responseAddrs []netip.Addr
|
responseAddrs []netip.Addr
|
||||||
cached bool
|
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
printResult := func() {
|
printResult := func() {
|
||||||
@@ -347,13 +346,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
err = E.Cause(err, "lookup ", domain)
|
err = E.Cause(err, "lookup ", domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseAddrs, cached = r.client.LookupCache(domain, options.Strategy)
|
|
||||||
if cached {
|
|
||||||
if len(responseAddrs) == 0 {
|
|
||||||
return nil, E.New("lookup ", domain, ": empty result (cached)")
|
|
||||||
}
|
|
||||||
return responseAddrs, nil
|
|
||||||
}
|
|
||||||
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
@@ -362,7 +354,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
transport := options.Transport
|
transport := options.Transport
|
||||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
if options.Strategy == C.DomainStrategyAsIS {
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
options.Strategy = r.defaultDomainStrategy
|
options.Strategy = legacyTransport.LegacyStrategy()
|
||||||
}
|
}
|
||||||
if !options.ClientSubnet.IsValid() {
|
if !options.ClientSubnet.IsValid() {
|
||||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
@@ -388,9 +380,11 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
return nil, &R.RejectedError{Cause: action.Error(ctx)}
|
return nil, &R.RejectedError{Cause: action.Error(ctx)}
|
||||||
case *R.RuleActionPredefined:
|
case *R.RuleActionPredefined:
|
||||||
|
responseAddrs = nil
|
||||||
if action.Rcode != mDNS.RcodeSuccess {
|
if action.Rcode != mDNS.RcodeSuccess {
|
||||||
err = RcodeError(action.Rcode)
|
err = RcodeError(action.Rcode)
|
||||||
} else {
|
} else {
|
||||||
|
err = nil
|
||||||
for _, answer := range action.Answer {
|
for _, answer := range action.Answer {
|
||||||
switch record := answer.(type) {
|
switch record := answer.(type) {
|
||||||
case *mDNS.A:
|
case *mDNS.A:
|
||||||
@@ -403,13 +397,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
goto response
|
goto response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var responseCheck func(responseAddrs []netip.Addr) bool
|
responseCheck := addressLimitResponseCheck(rule, metadata)
|
||||||
if rule != nil && rule.WithAddressLimit() {
|
|
||||||
responseCheck = func(responseAddrs []netip.Addr) bool {
|
|
||||||
metadata.DestinationAddresses = responseAddrs
|
|
||||||
return rule.MatchAddressLimit(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||||
}
|
}
|
||||||
@@ -437,6 +425,18 @@ func isAddressQuery(message *mDNS.Msg) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool {
|
||||||
|
if rule == nil || !rule.WithAddressLimit() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
responseMetadata := *metadata
|
||||||
|
return func(responseAddrs []netip.Addr) bool {
|
||||||
|
checkMetadata := responseMetadata
|
||||||
|
checkMetadata.DestinationAddresses = responseAddrs
|
||||||
|
return rule.MatchAddressLimit(&checkMetadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) ClearCache() {
|
func (r *Router) ClearCache() {
|
||||||
r.client.ClearCache()
|
r.client.ClearCache()
|
||||||
if r.platformInterface != nil {
|
if r.platformInterface != nil {
|
||||||
@@ -455,6 +455,6 @@ func (r *Router) LookupReverseMapping(ip netip.Addr) (string, bool) {
|
|||||||
func (r *Router) ResetNetwork() {
|
func (r *Router) ResetNetwork() {
|
||||||
r.ClearCache()
|
r.ClearCache()
|
||||||
for _, transport := range r.transport.Transports() {
|
for _, transport := range r.transport.Transports() {
|
||||||
transport.Close()
|
transport.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
dns/transport/base.go
Normal file
145
dns/transport/base.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransportState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
StateNew TransportState = iota
|
||||||
|
StateStarted
|
||||||
|
StateClosing
|
||||||
|
StateClosed
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTransportClosed = os.ErrClosed
|
||||||
|
ErrConnectionReset = E.New("connection reset")
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseTransport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
Logger logger.ContextLogger
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
state TransportState
|
||||||
|
inFlight int32
|
||||||
|
queriesComplete chan struct{}
|
||||||
|
closeCtx context.Context
|
||||||
|
closeCancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBaseTransport(adapter dns.TransportAdapter, logger logger.ContextLogger) *BaseTransport {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return &BaseTransport{
|
||||||
|
TransportAdapter: adapter,
|
||||||
|
Logger: logger,
|
||||||
|
state: StateNew,
|
||||||
|
closeCtx: ctx,
|
||||||
|
closeCancel: cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) State() TransportState {
|
||||||
|
t.mutex.Lock()
|
||||||
|
defer t.mutex.Unlock()
|
||||||
|
return t.state
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) SetStarted() error {
|
||||||
|
t.mutex.Lock()
|
||||||
|
defer t.mutex.Unlock()
|
||||||
|
switch t.state {
|
||||||
|
case StateNew:
|
||||||
|
t.state = StateStarted
|
||||||
|
return nil
|
||||||
|
case StateStarted:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return ErrTransportClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) BeginQuery() bool {
|
||||||
|
t.mutex.Lock()
|
||||||
|
defer t.mutex.Unlock()
|
||||||
|
if t.state != StateStarted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
t.inFlight++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) EndQuery() {
|
||||||
|
t.mutex.Lock()
|
||||||
|
if t.inFlight > 0 {
|
||||||
|
t.inFlight--
|
||||||
|
}
|
||||||
|
if t.inFlight == 0 && t.queriesComplete != nil {
|
||||||
|
close(t.queriesComplete)
|
||||||
|
t.queriesComplete = nil
|
||||||
|
}
|
||||||
|
t.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) CloseContext() context.Context {
|
||||||
|
return t.closeCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) Shutdown(ctx context.Context) error {
|
||||||
|
t.mutex.Lock()
|
||||||
|
|
||||||
|
if t.state >= StateClosing {
|
||||||
|
t.mutex.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.state == StateNew {
|
||||||
|
t.state = StateClosed
|
||||||
|
t.mutex.Unlock()
|
||||||
|
t.closeCancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.state = StateClosing
|
||||||
|
|
||||||
|
if t.inFlight == 0 {
|
||||||
|
t.state = StateClosed
|
||||||
|
t.mutex.Unlock()
|
||||||
|
t.closeCancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.queriesComplete = make(chan struct{})
|
||||||
|
queriesComplete := t.queriesComplete
|
||||||
|
t.mutex.Unlock()
|
||||||
|
|
||||||
|
t.closeCancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-queriesComplete:
|
||||||
|
t.mutex.Lock()
|
||||||
|
t.state = StateClosed
|
||||||
|
t.mutex.Unlock()
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.mutex.Lock()
|
||||||
|
t.state = StateClosed
|
||||||
|
t.mutex.Unlock()
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTransport) Close() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout)
|
||||||
|
defer cancel()
|
||||||
|
return t.Shutdown(ctx)
|
||||||
|
}
|
||||||
287
dns/transport/connector.go
Normal file
287
dns/transport/connector.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectorCallbacks[T any] struct {
|
||||||
|
IsClosed func(connection T) bool
|
||||||
|
Close func(connection T)
|
||||||
|
Reset func(connection T)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connector[T any] struct {
|
||||||
|
dial func(ctx context.Context) (T, error)
|
||||||
|
callbacks ConnectorCallbacks[T]
|
||||||
|
|
||||||
|
access sync.Mutex
|
||||||
|
connection T
|
||||||
|
hasConnection bool
|
||||||
|
connectionCancel context.CancelFunc
|
||||||
|
connecting chan struct{}
|
||||||
|
|
||||||
|
closeCtx context.Context
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnector[T any](closeCtx context.Context, dial func(context.Context) (T, error), callbacks ConnectorCallbacks[T]) *Connector[T] {
|
||||||
|
return &Connector[T]{
|
||||||
|
dial: dial,
|
||||||
|
callbacks: callbacks,
|
||||||
|
closeCtx: closeCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSingleflightConnector(closeCtx context.Context, dial func(context.Context) (*Connection, error)) *Connector[*Connection] {
|
||||||
|
return NewConnector(closeCtx, dial, ConnectorCallbacks[*Connection]{
|
||||||
|
IsClosed: func(connection *Connection) bool {
|
||||||
|
return connection.IsClosed()
|
||||||
|
},
|
||||||
|
Close: func(connection *Connection) {
|
||||||
|
connection.CloseWithError(ErrTransportClosed)
|
||||||
|
},
|
||||||
|
Reset: func(connection *Connection) {
|
||||||
|
connection.CloseWithError(ErrConnectionReset)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKeyConnecting struct{}
|
||||||
|
|
||||||
|
var errRecursiveConnectorDial = E.New("recursive connector dial")
|
||||||
|
|
||||||
|
func (c *Connector[T]) Get(ctx context.Context) (T, error) {
|
||||||
|
var zero T
|
||||||
|
for {
|
||||||
|
c.access.Lock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
c.access.Unlock()
|
||||||
|
return zero, ErrTransportClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.hasConnection && !c.callbacks.IsClosed(c.connection) {
|
||||||
|
connection := c.connection
|
||||||
|
c.access.Unlock()
|
||||||
|
return connection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.hasConnection = false
|
||||||
|
if c.connectionCancel != nil {
|
||||||
|
c.connectionCancel()
|
||||||
|
c.connectionCancel = nil
|
||||||
|
}
|
||||||
|
if isRecursiveConnectorDial(ctx, c) {
|
||||||
|
c.access.Unlock()
|
||||||
|
return zero, errRecursiveConnectorDial
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.connecting != nil {
|
||||||
|
connecting := c.connecting
|
||||||
|
c.access.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-connecting:
|
||||||
|
continue
|
||||||
|
case <-ctx.Done():
|
||||||
|
return zero, ctx.Err()
|
||||||
|
case <-c.closeCtx.Done():
|
||||||
|
return zero, ErrTransportClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
c.access.Unlock()
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.connecting = make(chan struct{})
|
||||||
|
c.access.Unlock()
|
||||||
|
|
||||||
|
dialContext := context.WithValue(ctx, contextKeyConnecting{}, c)
|
||||||
|
connection, cancel, err := c.dialWithCancellation(dialContext)
|
||||||
|
|
||||||
|
c.access.Lock()
|
||||||
|
close(c.connecting)
|
||||||
|
c.connecting = nil
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.access.Unlock()
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
cancel()
|
||||||
|
c.callbacks.Close(connection)
|
||||||
|
c.access.Unlock()
|
||||||
|
return zero, ErrTransportClosed
|
||||||
|
}
|
||||||
|
if err = ctx.Err(); err != nil {
|
||||||
|
cancel()
|
||||||
|
c.callbacks.Close(connection)
|
||||||
|
c.access.Unlock()
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.connection = connection
|
||||||
|
c.hasConnection = true
|
||||||
|
c.connectionCancel = cancel
|
||||||
|
result := c.connection
|
||||||
|
c.access.Unlock()
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRecursiveConnectorDial[T any](ctx context.Context, connector *Connector[T]) bool {
|
||||||
|
dialConnector, loaded := ctx.Value(contextKeyConnecting{}).(*Connector[T])
|
||||||
|
return loaded && dialConnector == connector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, context.CancelFunc, error) {
|
||||||
|
var zero T
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return zero, nil, err
|
||||||
|
}
|
||||||
|
connCtx, cancel := context.WithCancel(c.closeCtx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
stateAccess sync.Mutex
|
||||||
|
dialComplete bool
|
||||||
|
)
|
||||||
|
stopCancel := context.AfterFunc(ctx, func() {
|
||||||
|
stateAccess.Lock()
|
||||||
|
if !dialComplete {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
stateAccess.Unlock()
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
stateAccess.Lock()
|
||||||
|
dialComplete = true
|
||||||
|
stateAccess.Unlock()
|
||||||
|
stopCancel()
|
||||||
|
cancel()
|
||||||
|
return zero, nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, err := c.dial(valueContext{connCtx, ctx})
|
||||||
|
stateAccess.Lock()
|
||||||
|
dialComplete = true
|
||||||
|
stateAccess.Unlock()
|
||||||
|
stopCancel()
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return zero, nil, err
|
||||||
|
}
|
||||||
|
return connection, cancel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type valueContext struct {
|
||||||
|
context.Context
|
||||||
|
parent context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v valueContext) Value(key any) any {
|
||||||
|
return v.parent.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v valueContext) Deadline() (time.Time, bool) {
|
||||||
|
return v.parent.Deadline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connector[T]) Close() error {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.closed = true
|
||||||
|
|
||||||
|
if c.connectionCancel != nil {
|
||||||
|
c.connectionCancel()
|
||||||
|
c.connectionCancel = nil
|
||||||
|
}
|
||||||
|
if c.hasConnection {
|
||||||
|
c.callbacks.Close(c.connection)
|
||||||
|
c.hasConnection = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connector[T]) Reset() {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
if c.connectionCancel != nil {
|
||||||
|
c.connectionCancel()
|
||||||
|
c.connectionCancel = nil
|
||||||
|
}
|
||||||
|
if c.hasConnection {
|
||||||
|
c.callbacks.Reset(c.connection)
|
||||||
|
c.hasConnection = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connection struct {
|
||||||
|
net.Conn
|
||||||
|
|
||||||
|
closeOnce sync.Once
|
||||||
|
done chan struct{}
|
||||||
|
closeError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapConnection(conn net.Conn) *Connection {
|
||||||
|
return &Connection{
|
||||||
|
Conn: conn,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) Done() <-chan struct{} {
|
||||||
|
return c.done
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) IsClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) CloseError() error {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
if c.closeError != nil {
|
||||||
|
return c.closeError
|
||||||
|
}
|
||||||
|
return ErrTransportClosed
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) Close() error {
|
||||||
|
return c.CloseWithError(ErrTransportClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) CloseWithError(err error) error {
|
||||||
|
var returnError error
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
c.closeError = err
|
||||||
|
returnError = c.Conn.Close()
|
||||||
|
close(c.done)
|
||||||
|
})
|
||||||
|
return returnError
|
||||||
|
}
|
||||||
263
dns/transport/connector_test.go
Normal file
263
dns/transport/connector_test.go
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testConnectorConnection struct{}
|
||||||
|
|
||||||
|
func TestConnectorRecursiveGetFailsFast(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
dialCount atomic.Int32
|
||||||
|
closeCount atomic.Int32
|
||||||
|
connector *Connector[*testConnectorConnection]
|
||||||
|
)
|
||||||
|
|
||||||
|
dial := func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
dialCount.Add(1)
|
||||||
|
_, err := connector.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
connector = NewConnector(context.Background(), dial, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {
|
||||||
|
closeCount.Add(1)
|
||||||
|
},
|
||||||
|
Reset: func(connection *testConnectorConnection) {
|
||||||
|
closeCount.Add(1)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := connector.Get(context.Background())
|
||||||
|
require.ErrorIs(t, err, errRecursiveConnectorDial)
|
||||||
|
require.EqualValues(t, 1, dialCount.Load())
|
||||||
|
require.EqualValues(t, 0, closeCount.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorRecursiveGetAcrossConnectorsAllowed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
outerDialCount atomic.Int32
|
||||||
|
innerDialCount atomic.Int32
|
||||||
|
outerConnector *Connector[*testConnectorConnection]
|
||||||
|
innerConnector *Connector[*testConnectorConnection]
|
||||||
|
)
|
||||||
|
|
||||||
|
innerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
innerDialCount.Add(1)
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
outerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
outerDialCount.Add(1)
|
||||||
|
_, err := innerConnector.Get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := outerConnector.Get(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 1, outerDialCount.Load())
|
||||||
|
require.EqualValues(t, 1, innerDialCount.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorDialContextPreservesValueAndDeadline(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dialValue any
|
||||||
|
dialDeadline time.Time
|
||||||
|
dialHasDeadline bool
|
||||||
|
)
|
||||||
|
|
||||||
|
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
dialValue = ctx.Value(contextKey{})
|
||||||
|
dialDeadline, dialHasDeadline = ctx.Deadline()
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
deadline := time.Now().Add(time.Minute)
|
||||||
|
requestContext, cancel := context.WithDeadline(context.WithValue(context.Background(), contextKey{}, "test-value"), deadline)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := connector.Get(requestContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "test-value", dialValue)
|
||||||
|
require.True(t, dialHasDeadline)
|
||||||
|
require.WithinDuration(t, deadline, dialDeadline, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorDialSkipsCanceledRequest(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var dialCount atomic.Int32
|
||||||
|
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
dialCount.Add(1)
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
requestContext, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
_, err := connector.Get(requestContext)
|
||||||
|
require.ErrorIs(t, err, context.Canceled)
|
||||||
|
require.EqualValues(t, 0, dialCount.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorCanceledRequestDoesNotCacheConnection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
dialCount atomic.Int32
|
||||||
|
closeCount atomic.Int32
|
||||||
|
)
|
||||||
|
dialStarted := make(chan struct{}, 1)
|
||||||
|
releaseDial := make(chan struct{})
|
||||||
|
|
||||||
|
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
dialCount.Add(1)
|
||||||
|
select {
|
||||||
|
case dialStarted <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
<-releaseDial
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {
|
||||||
|
closeCount.Add(1)
|
||||||
|
},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
requestContext, cancel := context.WithCancel(context.Background())
|
||||||
|
result := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
_, err := connector.Get(requestContext)
|
||||||
|
result <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-dialStarted
|
||||||
|
cancel()
|
||||||
|
close(releaseDial)
|
||||||
|
|
||||||
|
err := <-result
|
||||||
|
require.ErrorIs(t, err, context.Canceled)
|
||||||
|
require.EqualValues(t, 1, dialCount.Load())
|
||||||
|
require.EqualValues(t, 1, closeCount.Load())
|
||||||
|
|
||||||
|
_, err = connector.Get(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, 2, dialCount.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorDialContextNotCanceledByRequestContextAfterDial(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var dialContext context.Context
|
||||||
|
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
dialContext = ctx
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
requestContext, cancel := context.WithCancel(context.Background())
|
||||||
|
_, err := connector.Get(requestContext)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, dialContext)
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-dialContext.Done():
|
||||||
|
t.Fatal("dial context canceled by request context after successful dial")
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
err = connector.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectorDialContextCanceledOnClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var dialContext context.Context
|
||||||
|
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
|
||||||
|
dialContext = ctx
|
||||||
|
return &testConnectorConnection{}, nil
|
||||||
|
}, ConnectorCallbacks[*testConnectorConnection]{
|
||||||
|
IsClosed: func(connection *testConnectorConnection) bool {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
Close: func(connection *testConnectorConnection) {},
|
||||||
|
Reset: func(connection *testConnectorConnection) {},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := connector.Get(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, dialContext)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-dialContext.Done():
|
||||||
|
t.Fatal("dial context canceled before connector close")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
err = connector.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-dialContext.Done():
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("dial context not canceled after connector close")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ type Transport struct {
|
|||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
transportLock sync.RWMutex
|
transportLock sync.RWMutex
|
||||||
updatedAt time.Time
|
updatedAt time.Time
|
||||||
|
lastError error
|
||||||
servers []M.Socksaddr
|
servers []M.Socksaddr
|
||||||
search []string
|
search []string
|
||||||
ndots int
|
ndots int
|
||||||
@@ -92,7 +93,7 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
_, err := t.Fetch()
|
_, err := t.fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.logger.Error(E.Cause(err, "fetch DNS servers"))
|
t.logger.Error(E.Cause(err, "fetch DNS servers"))
|
||||||
}
|
}
|
||||||
@@ -107,8 +108,15 @@ func (t *Transport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
t.transportLock.Lock()
|
||||||
|
t.updatedAt = time.Time{}
|
||||||
|
t.servers = nil
|
||||||
|
t.transportLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
servers, err := t.Fetch()
|
servers, err := t.fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -128,11 +136,20 @@ func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Fetch() ([]M.Socksaddr, error) {
|
func (t *Transport) Fetch() []M.Socksaddr {
|
||||||
|
servers, _ := t.fetch()
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) fetch() ([]M.Socksaddr, error) {
|
||||||
t.transportLock.RLock()
|
t.transportLock.RLock()
|
||||||
updatedAt := t.updatedAt
|
updatedAt := t.updatedAt
|
||||||
|
lastError := t.lastError
|
||||||
servers := t.servers
|
servers := t.servers
|
||||||
t.transportLock.RUnlock()
|
t.transportLock.RUnlock()
|
||||||
|
if lastError != nil {
|
||||||
|
return nil, lastError
|
||||||
|
}
|
||||||
if time.Since(updatedAt) < C.DHCPTTL {
|
if time.Since(updatedAt) < C.DHCPTTL {
|
||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
@@ -143,7 +160,7 @@ func (t *Transport) Fetch() ([]M.Socksaddr, error) {
|
|||||||
}
|
}
|
||||||
err := t.updateServers()
|
err := t.updateServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return servers, err
|
||||||
}
|
}
|
||||||
return t.servers, nil
|
return t.servers, nil
|
||||||
}
|
}
|
||||||
@@ -173,12 +190,15 @@ func (t *Transport) updateServers() error {
|
|||||||
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
||||||
err = t.fetchServers0(fetchCtx, iface)
|
err = t.fetchServers0(fetchCtx, iface)
|
||||||
cancel()
|
cancel()
|
||||||
|
t.updatedAt = time.Now()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
t.lastError = err
|
||||||
return err
|
return err
|
||||||
} else if len(t.servers) == 0 {
|
} else if len(t.servers) == 0 {
|
||||||
return E.New("dhcp: empty DNS servers response")
|
t.lastError = E.New("dhcp: empty DNS servers response")
|
||||||
|
return t.lastError
|
||||||
} else {
|
} else {
|
||||||
t.updatedAt = time.Now()
|
t.lastError = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,6 +263,7 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
buffer.Reset()
|
||||||
_, _, err := buffer.ReadPacketFrom(packetConn)
|
_, _, err := buffer.ReadPacketFrom(packetConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.ErrShortBuffer) {
|
if errors.Is(err, io.ErrShortBuffer) {
|
||||||
|
|||||||
@@ -82,8 +82,12 @@ func (s *MemoryStorage) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MemoryStorage) FakeIPReset() error {
|
func (s *MemoryStorage) FakeIPReset() error {
|
||||||
|
s.addressAccess.Lock()
|
||||||
|
s.domainAccess.Lock()
|
||||||
s.addressCache = make(map[netip.Addr]string)
|
s.addressCache = make(map[netip.Addr]string)
|
||||||
s.domainCache4 = make(map[string]netip.Addr)
|
s.domainCache4 = make(map[string]netip.Addr)
|
||||||
s.domainCache6 = make(map[string]netip.Addr)
|
s.domainCache6 = make(map[string]netip.Addr)
|
||||||
|
s.domainAccess.Unlock()
|
||||||
|
s.addressAccess.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package fakeip
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -13,22 +14,49 @@ import (
|
|||||||
var _ adapter.FakeIPStore = (*Store)(nil)
|
var _ adapter.FakeIPStore = (*Store)(nil)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
inet4Range netip.Prefix
|
inet4Range netip.Prefix
|
||||||
inet6Range netip.Prefix
|
inet6Range netip.Prefix
|
||||||
storage adapter.FakeIPStorage
|
inet4Last netip.Addr
|
||||||
inet4Current netip.Addr
|
inet6Last netip.Addr
|
||||||
inet6Current netip.Addr
|
storage adapter.FakeIPStorage
|
||||||
|
|
||||||
|
addressAccess sync.Mutex
|
||||||
|
inet4Current netip.Addr
|
||||||
|
inet6Current netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store {
|
func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store {
|
||||||
return &Store{
|
store := &Store{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
inet4Range: inet4Range,
|
inet4Range: inet4Range,
|
||||||
inet6Range: inet6Range,
|
inet6Range: inet6Range,
|
||||||
}
|
}
|
||||||
|
if inet4Range.IsValid() {
|
||||||
|
store.inet4Last = broadcastAddress(inet4Range)
|
||||||
|
}
|
||||||
|
if inet6Range.IsValid() {
|
||||||
|
store.inet6Last = broadcastAddress(inet6Range)
|
||||||
|
}
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func broadcastAddress(prefix netip.Prefix) netip.Addr {
|
||||||
|
addr := prefix.Addr()
|
||||||
|
raw := addr.As16()
|
||||||
|
bits := prefix.Bits()
|
||||||
|
if addr.Is4() {
|
||||||
|
bits += 96
|
||||||
|
}
|
||||||
|
for i := bits; i < 128; i++ {
|
||||||
|
raw[i/8] |= 1 << (7 - i%8)
|
||||||
|
}
|
||||||
|
if addr.Is4() {
|
||||||
|
return netip.AddrFrom4([4]byte(raw[12:]))
|
||||||
|
}
|
||||||
|
return netip.AddrFrom16(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Start() error {
|
func (s *Store) Start() error {
|
||||||
@@ -46,10 +74,10 @@ func (s *Store) Start() error {
|
|||||||
s.inet6Current = metadata.Inet6Current
|
s.inet6Current = metadata.Inet6Current
|
||||||
} else {
|
} else {
|
||||||
if s.inet4Range.IsValid() {
|
if s.inet4Range.IsValid() {
|
||||||
s.inet4Current = s.inet4Range.Addr().Next().Next()
|
s.inet4Current = s.inet4Range.Addr().Next()
|
||||||
}
|
}
|
||||||
if s.inet6Range.IsValid() {
|
if s.inet6Range.IsValid() {
|
||||||
s.inet6Current = s.inet6Range.Addr().Next().Next()
|
s.inet6Current = s.inet6Range.Addr().Next()
|
||||||
}
|
}
|
||||||
_ = storage.FakeIPReset()
|
_ = storage.FakeIPReset()
|
||||||
}
|
}
|
||||||
@@ -65,25 +93,37 @@ func (s *Store) Close() error {
|
|||||||
if s.storage == nil {
|
if s.storage == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.storage.FakeIPSaveMetadata(&adapter.FakeIPMetadata{
|
s.addressAccess.Lock()
|
||||||
|
metadata := &adapter.FakeIPMetadata{
|
||||||
Inet4Range: s.inet4Range,
|
Inet4Range: s.inet4Range,
|
||||||
Inet6Range: s.inet6Range,
|
Inet6Range: s.inet6Range,
|
||||||
Inet4Current: s.inet4Current,
|
Inet4Current: s.inet4Current,
|
||||||
Inet6Current: s.inet6Current,
|
Inet6Current: s.inet6Current,
|
||||||
})
|
}
|
||||||
|
s.addressAccess.Unlock()
|
||||||
|
return s.storage.FakeIPSaveMetadata(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {
|
func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {
|
||||||
if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded {
|
if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded {
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.addressAccess.Lock()
|
||||||
|
defer s.addressAccess.Unlock()
|
||||||
|
|
||||||
|
// Double-check after acquiring lock
|
||||||
|
if address, loaded := s.storage.FakeIPLoadDomain(domain, isIPv6); loaded {
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
var address netip.Addr
|
var address netip.Addr
|
||||||
if !isIPv6 {
|
if !isIPv6 {
|
||||||
if !s.inet4Current.IsValid() {
|
if !s.inet4Current.IsValid() {
|
||||||
return netip.Addr{}, E.New("missing IPv4 fakeip address range")
|
return netip.Addr{}, E.New("missing IPv4 fakeip address range")
|
||||||
}
|
}
|
||||||
nextAddress := s.inet4Current.Next()
|
nextAddress := s.inet4Current.Next()
|
||||||
if !s.inet4Range.Contains(nextAddress) {
|
if nextAddress == s.inet4Last || !s.inet4Range.Contains(nextAddress) {
|
||||||
nextAddress = s.inet4Range.Addr().Next().Next()
|
nextAddress = s.inet4Range.Addr().Next().Next()
|
||||||
}
|
}
|
||||||
s.inet4Current = nextAddress
|
s.inet4Current = nextAddress
|
||||||
@@ -93,13 +133,16 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {
|
|||||||
return netip.Addr{}, E.New("missing IPv6 fakeip address range")
|
return netip.Addr{}, E.New("missing IPv6 fakeip address range")
|
||||||
}
|
}
|
||||||
nextAddress := s.inet6Current.Next()
|
nextAddress := s.inet6Current.Next()
|
||||||
if !s.inet6Range.Contains(nextAddress) {
|
if nextAddress == s.inet6Last || !s.inet6Range.Contains(nextAddress) {
|
||||||
nextAddress = s.inet6Range.Addr().Next().Next()
|
nextAddress = s.inet6Range.Addr().Next().Next()
|
||||||
}
|
}
|
||||||
s.inet6Current = nextAddress
|
s.inet6Current = nextAddress
|
||||||
address = nextAddress
|
address = nextAddress
|
||||||
}
|
}
|
||||||
s.storage.FakeIPStoreAsync(address, domain, s.logger)
|
err := s.storage.FakeIPStore(address, domain)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("save FakeIP cache: ", err)
|
||||||
|
}
|
||||||
s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{
|
s.storage.FakeIPSaveMetadataAsync(&adapter.FakeIPMetadata{
|
||||||
Inet4Range: s.inet4Range,
|
Inet4Range: s.inet4Range,
|
||||||
Inet6Range: s.inet6Range,
|
Inet6Range: s.inet6Range,
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ func (t *Transport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := mDNS.CanonicalName(question.Name)
|
domain := mDNS.CanonicalName(question.Name)
|
||||||
|
|||||||
@@ -145,6 +145,13 @@ func (t *HTTPSTransport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *HTTPSTransport) Reset() {
|
||||||
|
t.transportAccess.Lock()
|
||||||
|
defer t.transportAccess.Unlock()
|
||||||
|
t.transport.CloseIdleConnections()
|
||||||
|
t.transport = t.transport.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
startAt := time.Now()
|
startAt := time.Now()
|
||||||
response, err := t.exchange(ctx, message)
|
response, err := t.exchange(ctx, message)
|
||||||
@@ -182,7 +189,10 @@ func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS
|
|||||||
request.Header = t.headers.Clone()
|
request.Header = t.headers.Clone()
|
||||||
request.Header.Set("Content-Type", MimeType)
|
request.Header.Set("Content-Type", MimeType)
|
||||||
request.Header.Set("Accept", MimeType)
|
request.Header.Set("Accept", MimeType)
|
||||||
response, err := t.transport.RoundTrip(request)
|
t.transportAccess.Lock()
|
||||||
|
currentTransport := t.transport
|
||||||
|
t.transportAccess.Unlock()
|
||||||
|
response, err := currentTransport.RoundTrip(request)
|
||||||
requestBuffer.Release()
|
requestBuffer.Release()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -194,12 +204,12 @@ func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS
|
|||||||
var responseMessage mDNS.Msg
|
var responseMessage mDNS.Msg
|
||||||
if response.ContentLength > 0 {
|
if response.ContentLength > 0 {
|
||||||
responseBuffer := buf.NewSize(int(response.ContentLength))
|
responseBuffer := buf.NewSize(int(response.ContentLength))
|
||||||
|
defer responseBuffer.Release()
|
||||||
_, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength))
|
_, err = responseBuffer.ReadFullFrom(response.Body, int(response.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = responseMessage.Unpack(responseBuffer.Bytes())
|
err = responseMessage.Unpack(responseBuffer.Bytes())
|
||||||
responseBuffer.Release()
|
|
||||||
} else {
|
} else {
|
||||||
rawMessage, err = io.ReadAll(response.Body)
|
rawMessage, err = io.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -76,12 +76,12 @@ func (t *Transport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
if t.resolved != nil {
|
if t.resolved != nil {
|
||||||
resolverObject := t.resolved.Object()
|
return t.resolved.Exchange(ctx, message)
|
||||||
if resolverObject != nil {
|
|
||||||
return t.resolved.Exchange(resolverObject, ctx, message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type Transport struct {
|
|||||||
|
|
||||||
type dhcpTransport interface {
|
type dhcpTransport interface {
|
||||||
adapter.DNSTransport
|
adapter.DNSTransport
|
||||||
Fetch() ([]M.Socksaddr, error)
|
Fetch() []M.Socksaddr
|
||||||
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,14 +74,12 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !C.IsIos {
|
if t.fallback {
|
||||||
if t.fallback {
|
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
||||||
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
if t.dhcpTransport != nil {
|
||||||
if t.dhcpTransport != nil {
|
err := t.dhcpTransport.Start(stage)
|
||||||
err := t.dhcpTransport.Start(stage)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,6 +92,12 @@ func (t *Transport) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
t.dhcpTransport.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
@@ -105,12 +109,10 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
if !t.fallback {
|
if !t.fallback {
|
||||||
return t.exchange(ctx, message, question.Name)
|
return t.exchange(ctx, message, question.Name)
|
||||||
}
|
}
|
||||||
if !C.IsIos {
|
if t.dhcpTransport != nil {
|
||||||
if t.dhcpTransport != nil {
|
dhcpTransports := t.dhcpTransport.Fetch()
|
||||||
dhcpTransports, _ := t.dhcpTransport.Fetch()
|
if len(dhcpTransports) > 0 {
|
||||||
if len(dhcpTransports) > 0 {
|
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
||||||
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.preferGo {
|
if t.preferGo {
|
||||||
@@ -134,9 +136,5 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
}
|
}
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
if C.IsIos {
|
return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.")
|
||||||
return nil, E.New("only A and AAAA queries are supported on iOS and tvOS when using NetworkExtension.")
|
|
||||||
} else {
|
|
||||||
return nil, E.New("only A and AAAA queries are supported on macOS when using NetworkExtension and DHCP unavailable.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user