mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-10 21:38:16 +03:00
Compare commits
95 Commits
v1.13.12-e
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0613431623 | ||
|
|
95aa5d05b7 | ||
|
|
0ccfb8ad96 | ||
|
|
86fe2aee98 | ||
|
|
1b2cce77f1 | ||
|
|
4b1a283bbf | ||
|
|
f91cbc0e42 | ||
|
|
7d1b88e1cd | ||
|
|
a4ad775fb7 | ||
|
|
88bfc6fd77 | ||
|
|
cd8501736d | ||
|
|
cc05b89b59 | ||
|
|
f8bfe56458 | ||
|
|
f9942b7c50 | ||
|
|
41915a6621 | ||
|
|
2d6894f28a | ||
|
|
93ceb33780 | ||
|
|
f643e35d00 | ||
|
|
cbcc4f4cfb | ||
|
|
feab546659 | ||
|
|
5a3be48d7b | ||
|
|
ef2f170364 | ||
|
|
5022d9a924 | ||
|
|
50d61c09cf | ||
|
|
bcc703310e | ||
|
|
a6c448e145 | ||
|
|
11105eb553 | ||
|
|
18e240e5b3 | ||
|
|
03bb284370 | ||
|
|
bc63aa2254 | ||
|
|
abbe2f7eaf | ||
|
|
33d576a018 | ||
|
|
38e34c7719 | ||
|
|
03d086c32e | ||
|
|
52de97edf1 | ||
|
|
0e68b1a29e | ||
|
|
6b43758d8e | ||
|
|
9957ab6e8b | ||
|
|
36aef228bf | ||
|
|
82ba228c39 | ||
|
|
7acc036c6e | ||
|
|
78439340cf | ||
|
|
7fd99aa515 | ||
|
|
5d30bfabae | ||
|
|
c29d56699f | ||
|
|
5c4518e6a4 | ||
|
|
58bf4c1e85 | ||
|
|
49d8408fdf | ||
|
|
01cbc4810a | ||
|
|
d9b5ca3e82 | ||
|
|
6c2b4dce4a | ||
|
|
daeb14bdd2 | ||
|
|
7d7aa4a328 | ||
|
|
f0f0b658f7 | ||
|
|
de2546f572 | ||
|
|
fcb8171ff5 | ||
|
|
a53c6a906f | ||
|
|
8637c536c8 | ||
|
|
db7a0c6092 | ||
|
|
87d287dd56 | ||
|
|
ee65df4dfc | ||
|
|
f9f5a0c724 | ||
|
|
96e81b6e76 | ||
|
|
883bea6628 | ||
|
|
62b98fcc4c | ||
|
|
c0d7198ac2 | ||
|
|
da1cdcb784 | ||
|
|
cd284a4368 | ||
|
|
33e9d719f8 | ||
|
|
f0a3ba5bc8 | ||
|
|
b96a6b595e | ||
|
|
be29171106 | ||
|
|
c66b32c859 | ||
|
|
984ebbb288 | ||
|
|
c5929cf948 | ||
|
|
be45dd2716 | ||
|
|
a20f525048 | ||
|
|
31ebe2f39f | ||
|
|
61b9da2f1a | ||
|
|
f5c738ea9d | ||
|
|
ebc783b580 | ||
|
|
a5b6dff812 | ||
|
|
cc4e90c861 | ||
|
|
2abce0959a | ||
|
|
a98cb0fd84 | ||
|
|
7b96406592 | ||
|
|
4fea063b6f | ||
|
|
1672d5a84a | ||
|
|
4a1b69c1d4 | ||
|
|
e2c1ff3a11 | ||
|
|
37dd8fa692 | ||
|
|
29b6a75f16 | ||
|
|
7575746f72 | ||
|
|
f35a58b7ee | ||
|
|
4aacdade73 |
@@ -14,7 +14,6 @@
|
|||||||
--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
23
.fpm_pacman
@@ -1,23 +0,0 @@
|
|||||||
-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
|
|
||||||
@@ -4,12 +4,10 @@
|
|||||||
--license GPL-3.0-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--vendor SagerNet
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
--config-files /etc/sing-box/config.json
|
--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/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
|||||||
1
.github/CRONET_GO_VERSION
vendored
1
.github/CRONET_GO_VERSION
vendored
@@ -1 +0,0 @@
|
|||||||
2faf34666c2cc8234f10f2ab6d4c4d6104d34ae2
|
|
||||||
94
.github/build_alpine_apk.sh
vendored
94
.github/build_alpine_apk.sh
vendored
@@ -1,94 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
prepare_apk_root() {
|
|
||||||
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
|
|
||||||
APK_ROOT_DIR=$(mktemp -d)
|
|
||||||
mkdir -p "$APK_ROOT_DIR/etc"
|
|
||||||
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
|
|
||||||
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
|
|
||||||
EOF
|
|
||||||
cat > "$APK_ROOT_DIR/etc/group" <<EOF
|
|
||||||
root:x:$(id -g):root
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
prepare_apk_root
|
|
||||||
trap 'rm -rf "$ROOT_DIR" "$APK_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 --root "$APK_ROOT_DIR" 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"
|
|
||||||
93
.github/build_openwrt_apk.sh
vendored
93
.github/build_openwrt_apk.sh
vendored
@@ -1,93 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
prepare_apk_root() {
|
|
||||||
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
|
|
||||||
APK_ROOT_DIR=$(mktemp -d)
|
|
||||||
mkdir -p "$APK_ROOT_DIR/etc"
|
|
||||||
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
|
|
||||||
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
|
|
||||||
EOF
|
|
||||||
cat > "$APK_ROOT_DIR/etc/group" <<EOF
|
|
||||||
root:x:$(id -g):root
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
prepare_apk_root
|
|
||||||
trap 'rm -rf "$ROOT_DIR" "$APK_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 --root "$APK_ROOT_DIR" 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"
|
|
||||||
33
.github/detect_track.sh
vendored
33
.github/detect_track.sh
vendored
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
branches=$(git branch -r --contains HEAD)
|
|
||||||
if echo "$branches" | grep -q 'origin/stable'; then
|
|
||||||
track=stable
|
|
||||||
elif echo "$branches" | grep -q 'origin/testing'; then
|
|
||||||
track=testing
|
|
||||||
elif echo "$branches" | grep -q 'origin/oldstable'; then
|
|
||||||
track=oldstable
|
|
||||||
else
|
|
||||||
echo "ERROR: HEAD is not on any known release branch (stable/testing/oldstable)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$track" == "stable" ]]; then
|
|
||||||
tag=$(git describe --tags --exact-match HEAD 2>/dev/null || true)
|
|
||||||
if [[ -n "$tag" && "$tag" == *"-"* ]]; then
|
|
||||||
track=beta
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$track" in
|
|
||||||
stable) name=sing-box; docker_tag=latest ;;
|
|
||||||
beta) name=sing-box-beta; docker_tag=latest-beta ;;
|
|
||||||
testing) name=sing-box-testing; docker_tag=latest-testing ;;
|
|
||||||
oldstable) name=sing-box-oldstable; docker_tag=latest-oldstable ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "track=${track} name=${name} docker_tag=${docker_tag}" >&2
|
|
||||||
echo "TRACK=${track}" >> "$GITHUB_ENV"
|
|
||||||
echo "NAME=${name}" >> "$GITHUB_ENV"
|
|
||||||
echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV"
|
|
||||||
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@@ -6,7 +6,7 @@
|
|||||||
":disableRateLimiting"
|
":disableRateLimiting"
|
||||||
],
|
],
|
||||||
"baseBranches": [
|
"baseBranches": [
|
||||||
"unstable"
|
"dev-next"
|
||||||
],
|
],
|
||||||
"golang": {
|
"golang": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
|
|||||||
45
.github/setup_go_for_macos1013.sh
vendored
45
.github/setup_go_for_macos1013.sh
vendored
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION="1.25.9"
|
|
||||||
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"
|
|
||||||
46
.github/setup_go_for_windows7.sh
vendored
46
.github/setup_go_for_windows7.sh
vendored
@@ -1,46 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION="1.25.9"
|
|
||||||
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"
|
|
||||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
|
||||||
mv go go_win7
|
|
||||||
cd go_win7
|
|
||||||
|
|
||||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
|
||||||
# these patch URLs only work on golang1.25.x
|
|
||||||
# that means after golang1.26 release it must be changed
|
|
||||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
|
||||||
# revert:
|
|
||||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
|
||||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
|
||||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
|
||||||
# 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\""
|
|
||||||
|
|
||||||
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
|
||||||
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
|
||||||
done
|
|
||||||
25
.github/setup_legacy_go.sh
vendored
Executable file
25
.github/setup_legacy_go.sh
vendored
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VERSION="1.23.6"
|
||||||
|
|
||||||
|
mkdir -p $HOME/go
|
||||||
|
cd $HOME/go
|
||||||
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
|
mv go go_legacy
|
||||||
|
cd go_legacy
|
||||||
|
|
||||||
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
|
# this patch file only works on golang1.23.x
|
||||||
|
# that means after golang1.24 release it must be changed
|
||||||
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||||
|
# revert:
|
||||||
|
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||||
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||||
13
.github/update_cronet.sh
vendored
13
.github/update_cronet.sh
vendored
@@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
|
||||||
13
.github/update_cronet_dev.sh
vendored
13
.github/update_cronet_dev.sh
vendored
@@ -1,13 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
514
.github/workflows/build.yml
vendored
514
.github/workflows/build.yml
vendored
@@ -25,9 +25,8 @@ on:
|
|||||||
- publish-android
|
- publish-android
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- stable
|
- main-next
|
||||||
- testing
|
- dev-next
|
||||||
- unstable
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
||||||
@@ -41,13 +40,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -70,134 +69,70 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, variant: purego, naive: true }
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
- { 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, openwrt: "mipsel_24kc_24kf" }
|
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat }
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
- { 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 }
|
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||||
- { 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 }
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
- { os: linux, arch: loong64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
- { os: windows, arch: amd64 }
|
||||||
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
- { os: windows, arch: amd64, legacy_go: true }
|
||||||
|
- { os: windows, arch: "386" }
|
||||||
|
- { os: windows, arch: "386", legacy_go: true }
|
||||||
|
- { os: windows, arch: arm64 }
|
||||||
|
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android23" }
|
- { os: darwin, arch: amd64 }
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" }
|
- { os: darwin, arch: arm64 }
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android23" }
|
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android23" }
|
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||||
|
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||||
|
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||||
|
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! matrix.legacy_win7 }}
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
- name: Cache Go for Windows 7
|
- name: Cache Legacy Go
|
||||||
if: matrix.legacy_win7
|
if: matrix.require_legacy_go
|
||||||
id: cache-go-for-windows7
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_win7
|
~/go/go_legacy
|
||||||
key: go_win7_1258
|
key: go_legacy_1236
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Legacy Go
|
||||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
if: matrix.legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_go_for_windows7.sh
|
.github/setup_legacy_go.sh
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Legacy Go 2
|
||||||
if: matrix.legacy_win7
|
if: matrix.legacy_go
|
||||||
run: |-
|
run: |-
|
||||||
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
|
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
|
||||||
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
|
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
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"
|
||||||
@@ -205,83 +140,15 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
|
||||||
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
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Set shared ldflags
|
- name: Build
|
||||||
run: |
|
if: matrix.os != 'android'
|
||||||
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 "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
./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"
|
||||||
@@ -301,7 +168,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 "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@@ -317,13 +184,8 @@ jobs:
|
|||||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
DIR_NAME="${DIR_NAME}-legacy"
|
||||||
fi
|
|
||||||
if [[ "${{ matrix.variant }}" == "glibc" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-glibc"
|
|
||||||
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-musl"
|
|
||||||
fi
|
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 }}"
|
||||||
@@ -375,7 +237,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_pacman .fpm
|
cp .fpm_systemd .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" \
|
||||||
@@ -396,30 +258,6 @@ 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
|
||||||
@@ -431,217 +269,32 @@ 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
|
|
||||||
run: rm -f dist/sing-box dist/libcronet.so
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
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) }}${{ matrix.variant && format('-{0}', matrix.variant) }}
|
|
||||||
path: "dist"
|
|
||||||
build_darwin:
|
|
||||||
name: Build Darwin binaries
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
|
||||||
runs-on: macos-latest
|
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { arch: amd64 }
|
|
||||||
- { arch: arm64 }
|
|
||||||
- { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" }
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
if: ${{ ! matrix.legacy_osx }}
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.25.3
|
|
||||||
- name: Cache Go for macOS 10.13
|
|
||||||
if: matrix.legacy_osx
|
|
||||||
id: cache-go-for-macos1013
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
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
|
|
||||||
run: |-
|
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
|
||||||
- name: Set build tags
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
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}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build
|
|
||||||
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: darwin
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Set name
|
|
||||||
run: |-
|
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-darwin-${{ matrix.arch }}"
|
|
||||||
if [[ -n "${{ matrix.legacy_name }}" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
|
||||||
fi
|
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Archive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd dist
|
|
||||||
mkdir -p "${DIR_NAME}"
|
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
|
||||||
cp sing-box "${DIR_NAME}"
|
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
|
||||||
rm -r "${DIR_NAME}"
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: rm dist/sing-box
|
run: rm dist/sing-box
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-darwin_${{ matrix.arch }}${{ 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_go && '-legacy' || '' }}
|
||||||
path: "dist"
|
|
||||||
build_windows:
|
|
||||||
name: Build Windows binaries
|
|
||||||
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
|
||||||
runs-on: windows-latest
|
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { arch: amd64, naive: true }
|
|
||||||
- { arch: "386" }
|
|
||||||
- { arch: arm64, naive: true }
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ^1.25.4
|
|
||||||
- name: Set tag
|
|
||||||
run: |-
|
|
||||||
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
|
|
||||||
- name: Build
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS
|
|
||||||
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box.exe -tags "$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: "0"
|
|
||||||
GOOS: windows
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build
|
|
||||||
if: ${{ !matrix.naive }}
|
|
||||||
run: |
|
|
||||||
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS
|
|
||||||
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
|
||||||
mkdir -p dist
|
|
||||||
go build -v -trimpath -o dist/sing-box.exe -tags "$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: "0"
|
|
||||||
GOOS: windows
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Extract libcronet.dll
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
$CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION
|
|
||||||
$env:CGO_ENABLED = "0"
|
|
||||||
go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist
|
|
||||||
- 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"
|
|
||||||
Copy-Item "dist/libcronet.dll" "dist/$DIR_NAME"
|
|
||||||
Compress-Archive -Path "dist/$DIR_NAME" -DestinationPath "dist/$DIR_NAME.zip"
|
|
||||||
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
|
|
||||||
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
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
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') && github.ref != 'refs/heads/oldstable'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -664,12 +317,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/stable' && github.event_name != 'workflow_dispatch'
|
if: github.ref == 'refs/heads/main-next' && 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/testing'
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -689,9 +342,9 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |-
|
run: |-
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp *.aar clients/android/app/libs
|
cp libbox.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
./gradlew :app:assembleOtherRelease :app:assembleOtherLegacyRelease
|
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
||||||
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 }}
|
||||||
@@ -699,18 +352,8 @@ 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/*.apk dist
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.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:
|
||||||
@@ -718,20 +361,20 @@ 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' && github.ref != 'refs/heads/oldstable'
|
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -754,12 +397,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/stable' && github.event_name != 'workflow_dispatch'
|
if: github.ref == 'refs/heads/main-next' && 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/testing'
|
if: github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/android
|
cd clients/android
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -772,7 +415,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 *.aar clients/android/app/libs
|
cp libbox.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
|
||||||
@@ -783,8 +426,7 @@ jobs:
|
|||||||
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
|
||||||
build_apple:
|
build_apple:
|
||||||
name: Build Apple clients
|
name: Build Apple clients
|
||||||
runs-on: macos-26
|
runs-on: macos-15
|
||||||
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:
|
||||||
@@ -822,7 +464,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -830,7 +472,15 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
|
- name: Setup Xcode stable
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
|
- name: Setup Xcode beta
|
||||||
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
|
run: |-
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -838,12 +488,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/stable' && github.event_name != 'workflow_dispatch'
|
if: matrix.if && github.ref == 'refs/heads/main-next' && 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/testing'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -929,7 +579,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/testing'
|
if: matrix.if && matrix.name != 'macOS-standalone' && github.event_name == 'workflow_dispatch' && github.ref =='refs/heads/dev-next'
|
||||||
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
|
||||||
@@ -965,18 +615,16 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
- build
|
- build
|
||||||
- build_darwin
|
|
||||||
- build_windows
|
|
||||||
- build_android
|
- build_android
|
||||||
- build_apple
|
- build_apple
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
@@ -999,7 +647,7 @@ 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: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: dist
|
path: dist
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
208
.github/workflows/docker.yml
vendored
208
.github/workflows/docker.yml
vendored
@@ -1,10 +1,6 @@
|
|||||||
name: Publish Docker Images
|
name: Publish Docker Images
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#push:
|
|
||||||
# branches:
|
|
||||||
# - stable
|
|
||||||
# - testing
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -17,25 +13,20 @@ env:
|
|||||||
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
REGISTRY_IMAGE: ghcr.io/sagernet/sing-box
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_binary:
|
build:
|
||||||
name: Build binary
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
platform:
|
||||||
# Naive-enabled builds (musl)
|
- linux/amd64
|
||||||
- { arch: amd64, naive: true, docker_platform: "linux/amd64" }
|
- linux/arm/v6
|
||||||
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
- linux/arm/v7
|
||||||
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
- linux/arm64
|
||||||
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
- linux/386
|
||||||
- { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" }
|
- linux/ppc64le
|
||||||
- { arch: riscv64, naive: true, docker_platform: "linux/riscv64" }
|
- linux/riscv64
|
||||||
- { arch: loong64, naive: true, docker_platform: "linux/loong64" }
|
- linux/s390x
|
||||||
# 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:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -48,146 +39,7 @@ jobs:
|
|||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.25.9
|
|
||||||
- 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:
|
|
||||||
- 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:
|
with:
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
ref: ${{ steps.ref.outputs.ref }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -195,16 +47,6 @@ 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
|
||||||
@@ -226,9 +68,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile.binary
|
|
||||||
build-args: |
|
build-args: |
|
||||||
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||||
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
|
||||||
@@ -244,10 +85,9 @@ 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_docker
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -259,15 +99,15 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "ref=$ref"
|
echo "ref=$ref"
|
||||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||||
- name: Checkout
|
if [[ $ref == *"-"* ]]; then
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
latest=latest-beta
|
||||||
with:
|
else
|
||||||
ref: ${{ steps.ref.outputs.ref }}
|
latest=latest
|
||||||
fetch-depth: 0
|
fi
|
||||||
- name: Detect track
|
echo "latest=$latest"
|
||||||
run: bash .github/detect_track.sh
|
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
@@ -281,15 +121,13 @@ 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 \
|
||||||
-t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
|
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
||||||
-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 }}:${{ env.DOCKER_TAG }}
|
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 }}
|
||||||
|
|||||||
61
.github/workflows/lint.yml
vendored
61
.github/workflows/lint.yml
vendored
@@ -3,75 +3,34 @@ name: Lint
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- oldstable
|
- stable-next
|
||||||
- stable
|
- main-next
|
||||||
- testing
|
- dev-next
|
||||||
- unstable
|
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- '!.github/workflows/lint.yml'
|
- '!.github/workflows/lint.yml'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- oldstable
|
- stable-next
|
||||||
- stable
|
- main-next
|
||||||
- testing
|
- dev-next
|
||||||
- unstable
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Lint ${{ matrix.goos }}/${{ matrix.goarch }}
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- goos: windows
|
|
||||||
goarch: amd64
|
|
||||||
- goos: windows
|
|
||||||
goarch: '386'
|
|
||||||
- goos: windows
|
|
||||||
goarch: arm64
|
|
||||||
- goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
- goos: linux
|
|
||||||
goarch: arm64
|
|
||||||
- goos: linux
|
|
||||||
goarch: arm
|
|
||||||
- goos: linux
|
|
||||||
goarch: '386'
|
|
||||||
- goos: darwin
|
|
||||||
goarch: amd64
|
|
||||||
- goos: darwin
|
|
||||||
goarch: arm64
|
|
||||||
- goos: android
|
|
||||||
goarch: arm64
|
|
||||||
# - goos: freebsd
|
|
||||||
# goarch: amd64
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25
|
go-version: ^1.24.3
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v6
|
||||||
env:
|
|
||||||
GOOS: ${{ matrix.goos }}
|
|
||||||
GOARCH: ${{ matrix.goarch }}
|
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
|
|||||||
117
.github/workflows/linux.yml
vendored
117
.github/workflows/linux.yml
vendored
@@ -1,10 +1,6 @@
|
|||||||
name: Build Linux Packages
|
name: Build Linux Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#push:
|
|
||||||
# branches:
|
|
||||||
# - stable
|
|
||||||
# - testing
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
@@ -23,13 +19,13 @@ jobs:
|
|||||||
version: ${{ steps.outputs.outputs.version }}
|
version: ${{ steps.outputs.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -51,68 +47,32 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Naive-enabled builds (musl)
|
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||||
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||||
- { 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@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.24.3
|
||||||
- name: Clone cronet-go
|
- name: Setup Android NDK
|
||||||
if: matrix.naive
|
if: matrix.os == 'android'
|
||||||
run: |
|
uses: nttld/setup-ndk@v1
|
||||||
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:
|
||||||
path: |
|
ndk-version: r28
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
local-cache: true
|
||||||
~/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"
|
||||||
@@ -120,38 +80,14 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api'
|
||||||
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: Set shared ldflags
|
- name: Build
|
||||||
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 "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
|
||||||
./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"
|
||||||
@@ -162,8 +98,14 @@ jobs:
|
|||||||
- name: Set mtime
|
- name: Set mtime
|
||||||
run: |-
|
run: |-
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
- name: Detect track
|
- name: Set name
|
||||||
run: bash .github/detect_track.sh
|
if: ${{ ! contains(needs.calculate_version.outputs.version, '-') }}
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
|
- name: Set beta name
|
||||||
|
if: contains(needs.calculate_version.outputs.version, '-')
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
- name: Set version
|
- name: Set version
|
||||||
run: |-
|
run: |-
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
@@ -224,7 +166,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
@@ -233,11 +175,10 @@ 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: Download builds
|
- name: Download builds
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
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/
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -12,14 +12,7 @@
|
|||||||
/*.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/
|
||||||
CLAUDE.md
|
|
||||||
AGENTS.md
|
|
||||||
/.claude/
|
|
||||||
dist
|
|
||||||
logs
|
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "clients/apple"]
|
||||||
|
path = clients/apple
|
||||||
|
url = https://github.com/SagerNet/sing-box-for-apple.git
|
||||||
|
[submodule "clients/android"]
|
||||||
|
path = clients/android
|
||||||
|
url = https://github.com/SagerNet/sing-box-for-android.git
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
version: "2"
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- gofumpt
|
||||||
|
- govet
|
||||||
|
- gci
|
||||||
|
- staticcheck
|
||||||
|
- paralleltest
|
||||||
|
- ineffassign
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
custom-order: true
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- prefix(github.com/sagernet/)
|
||||||
|
- default
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- -SA1003
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@@ -9,48 +30,7 @@ run:
|
|||||||
- with_utls
|
- with_utls
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
|
||||||
- with_ccm
|
issues:
|
||||||
- with_ocm
|
exclude-dirs:
|
||||||
- badlinkname
|
- transport/simple-obfs
|
||||||
- tfogo_checklinkname0
|
|
||||||
linters:
|
|
||||||
default: none
|
|
||||||
enable:
|
|
||||||
- ineffassign
|
|
||||||
- paralleltest
|
|
||||||
- staticcheck
|
|
||||||
- unused
|
|
||||||
- modernize
|
|
||||||
settings:
|
|
||||||
modernize:
|
|
||||||
disable:
|
|
||||||
- omitzero # nested struct omitempty -> omitzero changes JSON output semantics
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- all
|
|
||||||
- -QF1008 # could remove embedded field "<interface>" from selector
|
|
||||||
- -ST1003 # should not use ALL_CAPS in Go names; use CamelCase instead
|
|
||||||
- -QF1001 # could apply De Morgan's law
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- \.pb\.go$
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gci
|
|
||||||
- gofumpt
|
|
||||||
settings:
|
|
||||||
gci:
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(github.com/sagernet/)
|
|
||||||
- default
|
|
||||||
custom-order: true
|
|
||||||
|
|||||||
103
.goreleaser.fury.yaml
Normal file
103
.goreleaser.fury.yaml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
project_name: sing-box
|
||||||
|
builds:
|
||||||
|
- id: main
|
||||||
|
main: ./cmd/sing-box
|
||||||
|
flags:
|
||||||
|
- -v
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
|
tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_utls
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
targets:
|
||||||
|
- linux_386
|
||||||
|
- linux_amd64_v1
|
||||||
|
- linux_arm64
|
||||||
|
- linux_arm_7
|
||||||
|
- linux_s390x
|
||||||
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Version }}.{{ .ShortCommit }}"
|
||||||
|
nfpms:
|
||||||
|
- &template
|
||||||
|
id: package
|
||||||
|
package_name: sing-box
|
||||||
|
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
builds:
|
||||||
|
- main
|
||||||
|
homepage: https://sing-box.sagernet.org/
|
||||||
|
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||||
|
description: The universal proxy platform.
|
||||||
|
license: GPLv3 or later
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
priority: extra
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: "config|noreplace"
|
||||||
|
|
||||||
|
- src: release/config/sing-box.service
|
||||||
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
|
- src: release/config/sing-box@.service
|
||||||
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
- src: release/config/sing-box.sysusers
|
||||||
|
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||||
|
- src: release/config/sing-box.rules
|
||||||
|
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
- src: release/config/sing-box-split-dns.xml
|
||||||
|
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
- src: LICENSE
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
deb:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
fields:
|
||||||
|
Bugs: https://github.com/SagerNet/sing-box/issues
|
||||||
|
rpm:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
conflicts:
|
||||||
|
- sing-box-beta
|
||||||
|
- id: package_beta
|
||||||
|
<<: *template
|
||||||
|
package_name: sing-box-beta
|
||||||
|
file_name_template: '{{ .ProjectName }}-beta_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
conflicts:
|
||||||
|
- sing-box
|
||||||
|
release:
|
||||||
|
disable: true
|
||||||
|
furies:
|
||||||
|
- account: sagernet
|
||||||
|
ids:
|
||||||
|
- package
|
||||||
|
disable: "{{ not (not .Prerelease) }}"
|
||||||
|
- account: sagernet
|
||||||
|
ids:
|
||||||
|
- package_beta
|
||||||
|
disable: "{{ not .Prerelease }}"
|
||||||
183
.goreleaser.yaml
183
.goreleaser.yaml
@@ -20,10 +20,6 @@ builds:
|
|||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
|
||||||
- with_mtproxy
|
|
||||||
- with_manager
|
|
||||||
- with_admin_panel
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOTOOLCHAIN=local
|
- GOTOOLCHAIN=local
|
||||||
@@ -35,13 +31,14 @@ builds:
|
|||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
- windows_arm64
|
- windows_arm64
|
||||||
- darwin_amd64_v1
|
- darwin_amd64_v1
|
||||||
- darwin_arm64
|
- darwin_arm64
|
||||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
- id: mips
|
- id: legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
@@ -52,15 +49,13 @@ builds:
|
|||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
env:
|
||||||
- with_mtproxy
|
- CGO_ENABLED=0
|
||||||
|
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
||||||
|
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- linux_mips
|
- windows_amd64_v1
|
||||||
- linux_mips_softfloat
|
- windows_386
|
||||||
- linux_mipsle
|
|
||||||
- linux_mipsle_softfloat
|
|
||||||
- linux_mips64
|
|
||||||
- linux_mips64le
|
|
||||||
- id: android
|
- id: android
|
||||||
<<: *template
|
<<: *template
|
||||||
env:
|
env:
|
||||||
@@ -94,80 +89,11 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- android_amd64
|
||||||
- id: compressed
|
|
||||||
<<: *template
|
|
||||||
targets:
|
|
||||||
- linux_386
|
|
||||||
- linux_amd64_v1
|
|
||||||
- linux_arm64
|
|
||||||
- linux_arm_6
|
|
||||||
- linux_arm_7
|
|
||||||
- linux_riscv64
|
|
||||||
- id: compressed-mips
|
|
||||||
<<: *template
|
|
||||||
tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_utls
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
- with_tailscale
|
|
||||||
- with_masque
|
|
||||||
- with_mtproxy
|
|
||||||
targets:
|
|
||||||
- linux_mips
|
|
||||||
- linux_mips_softfloat
|
|
||||||
- linux_mipsle
|
|
||||||
- linux_mipsle_softfloat
|
|
||||||
- id: compressed-android
|
|
||||||
<<: *template
|
|
||||||
env:
|
|
||||||
- CGO_ENABLED=1
|
|
||||||
- GOTOOLCHAIN=local
|
|
||||||
overrides:
|
|
||||||
- goos: android
|
|
||||||
goarch: arm
|
|
||||||
goarm: 7
|
|
||||||
env:
|
|
||||||
- CC=armv7a-linux-androideabi21-clang
|
|
||||||
- CXX=armv7a-linux-androideabi21-clang++
|
|
||||||
- goos: android
|
|
||||||
goarch: arm64
|
|
||||||
env:
|
|
||||||
- CC=aarch64-linux-android21-clang
|
|
||||||
- CXX=aarch64-linux-android21-clang++
|
|
||||||
- goos: android
|
|
||||||
goarch: 386
|
|
||||||
env:
|
|
||||||
- CC=i686-linux-android21-clang
|
|
||||||
- CXX=i686-linux-android21-clang++
|
|
||||||
- goos: android
|
|
||||||
goarch: amd64
|
|
||||||
goamd64: v1
|
|
||||||
env:
|
|
||||||
- CC=x86_64-linux-android21-clang
|
|
||||||
- CXX=x86_64-linux-android21-clang++
|
|
||||||
targets:
|
|
||||||
- android_arm_7
|
|
||||||
- android_arm64
|
|
||||||
- android_386
|
|
||||||
- android_amd64
|
|
||||||
upx:
|
|
||||||
- enabled: true
|
|
||||||
ids:
|
|
||||||
- compressed
|
|
||||||
- compressed-mips
|
|
||||||
- compressed-android
|
|
||||||
compress: best
|
|
||||||
lzma: true
|
|
||||||
archives:
|
archives:
|
||||||
- &template
|
- &template
|
||||||
id: archive
|
id: archive
|
||||||
builds:
|
builds:
|
||||||
- main
|
- main
|
||||||
- mips
|
|
||||||
- android
|
- android
|
||||||
formats:
|
formats:
|
||||||
- tar.gz
|
- tar.gz
|
||||||
@@ -178,19 +104,91 @@ archives:
|
|||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
- id: archive-legacy
|
- id: archive-legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
- legacy
|
- legacy
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||||
- id: archive-compressed
|
nfpms:
|
||||||
<<: *template
|
- id: package
|
||||||
|
package_name: sing-box
|
||||||
|
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
builds:
|
builds:
|
||||||
- compressed
|
- main
|
||||||
- compressed-mips
|
homepage: https://sing-box.sagernet.org/
|
||||||
- compressed-android
|
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-compressed'
|
description: The universal proxy platform.
|
||||||
|
license: GPLv3 or later
|
||||||
|
formats:
|
||||||
|
- deb
|
||||||
|
- rpm
|
||||||
|
- archlinux
|
||||||
|
# - apk
|
||||||
|
# - ipk
|
||||||
|
priority: extra
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: "config|noreplace"
|
||||||
|
|
||||||
|
- src: release/config/sing-box.service
|
||||||
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
|
- src: release/config/sing-box@.service
|
||||||
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
|
- src: release/config/sing-box.sysusers
|
||||||
|
dst: /usr/lib/sysusers.d/sing-box.conf
|
||||||
|
- src: release/config/sing-box.rules
|
||||||
|
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
||||||
|
- src: release/config/sing-box-split-dns.xml
|
||||||
|
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
- src: LICENSE
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
deb:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
fields:
|
||||||
|
Bugs: https://github.com/SagerNet/sing-box/issues
|
||||||
|
rpm:
|
||||||
|
signature:
|
||||||
|
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
||||||
|
overrides:
|
||||||
|
apk:
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- src: release/config/sing-box.initd
|
||||||
|
dst: /etc/init.d/sing-box
|
||||||
|
|
||||||
|
- src: release/completions/sing-box.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
- src: release/completions/sing-box.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
- src: release/completions/sing-box.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_sing-box
|
||||||
|
|
||||||
|
- src: LICENSE
|
||||||
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
ipk:
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- src: release/config/openwrt.init
|
||||||
|
dst: /etc/init.d/sing-box
|
||||||
|
- src: release/config/openwrt.conf
|
||||||
|
dst: /etc/config/sing-box
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
@@ -202,13 +200,14 @@ signs:
|
|||||||
- artifacts: checksum
|
- artifacts: checksum
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: shtorm-7
|
owner: SagerNet
|
||||||
name: sing-box-extended
|
name: sing-box
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
mode: replace
|
mode: replace
|
||||||
ids:
|
ids:
|
||||||
- archive
|
- archive
|
||||||
- archive-compressed
|
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
|
partial:
|
||||||
|
by: target
|
||||||
24
DONATE.md
24
DONATE.md
@@ -1,24 +0,0 @@
|
|||||||
# Support the project
|
|
||||||
|
|
||||||
If you want to support the project, you can donate to the following addresses.
|
|
||||||
|
|
||||||
### TRX (Tron)
|
|
||||||
```
|
|
||||||
TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F
|
|
||||||
```
|
|
||||||
### TON
|
|
||||||
```
|
|
||||||
UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1
|
|
||||||
```
|
|
||||||
### Solana
|
|
||||||
```
|
|
||||||
CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL
|
|
||||||
```
|
|
||||||
### Bitcoin
|
|
||||||
```
|
|
||||||
bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x
|
|
||||||
```
|
|
||||||
### Ethereum
|
|
||||||
```
|
|
||||||
0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6
|
|
||||||
```
|
|
||||||
17
Dockerfile
17
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||||
LABEL maintainer="shtorm-7"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
@@ -12,15 +12,16 @@ 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) \
|
||||||
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
|
&& go build -v -trimpath -tags \
|
||||||
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
|
||||||
&& 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\" $LDFLAGS_SHARED -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="shtorm-7"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
|
&& apk upgrade \
|
||||||
|
&& apk add bash tzdata ca-certificates nftables \
|
||||||
|
&& rm -rf /var/cache/apk/*
|
||||||
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
COPY --from=builder /go/bin/sing-box /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
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"]
|
|
||||||
201
Makefile
201
Makefile
@@ -1,26 +1,15 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
LDFLAGS_SHARED = $(shell cat release/LDFLAGS)
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
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
|
|
||||||
|
|
||||||
ADMIN_PANEL_DIR = service/admin_panel
|
|
||||||
ADMIN_PANEL_WEB = $(ADMIN_PANEL_DIR)/web
|
|
||||||
ADMIN_PANEL_DIST = $(ADMIN_PANEL_DIR)/dist
|
|
||||||
ADMIN_PANEL_TAGS = $(TAGS),with_admin_panel
|
|
||||||
|
|
||||||
DOCKER_IMAGE ?= shtorm7/sing-box-extended
|
|
||||||
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
|
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
@@ -28,17 +17,6 @@ build:
|
|||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
build_admin_panel:
|
|
||||||
cd $(ADMIN_PANEL_WEB) && \
|
|
||||||
npm install --no-fund --no-audit && \
|
|
||||||
npm run build
|
|
||||||
go run ./cmd/internal/admin_panel_pack \
|
|
||||||
-dir $(ADMIN_PANEL_DIST)
|
|
||||||
|
|
||||||
race:
|
|
||||||
export GOTOOLCHAIN=local && \
|
|
||||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(PARAMS) $(MAIN) && \
|
go build $(PARAMS) $(MAIN) && \
|
||||||
@@ -51,20 +29,23 @@ install:
|
|||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
@golangci-lint fmt
|
@gofumpt -l -w .
|
||||||
|
@gofmt -s -w .
|
||||||
|
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||||
|
|
||||||
fmt_docs:
|
fmt_install:
|
||||||
go run ./cmd/internal/format_docs
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
|
go install -v github.com/daixiang0/gci@latest
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
GOOS=linux golangci-lint run ./...
|
GOOS=linux golangci-lint run ./...
|
||||||
GOOS=android golangci-lint run ./...
|
GOOS=android golangci-lint run ./...
|
||||||
GOOS=windows golangci-lint run ./...
|
GOOS=windows golangci-lint run ./...
|
||||||
GOOS=darwin golangci-lint run ./...
|
GOOS=darwin golangci-lint run ./...
|
||||||
# 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@latest
|
go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@@ -78,11 +59,15 @@ proto_install:
|
|||||||
update_certificates:
|
update_certificates:
|
||||||
go run ./cmd/internal/update_certificates
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release: build_admin_panel
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz \
|
mv dist/*.tar.gz \
|
||||||
dist/*.zip \
|
dist/*.zip \
|
||||||
|
dist/*.deb \
|
||||||
|
dist/*.rpm \
|
||||||
|
dist/*_amd64.pkg.tar.zst \
|
||||||
|
dist/*_arm64.pkg.tar.zst \
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
@@ -93,29 +78,20 @@ release_repo:
|
|||||||
release_install:
|
release_install:
|
||||||
go install -v github.com/tcnksm/ghr@latest
|
go install -v github.com/tcnksm/ghr@latest
|
||||||
|
|
||||||
release_docker:
|
|
||||||
sudo docker buildx build \
|
|
||||||
--platform $(DOCKER_PLATFORMS) \
|
|
||||||
-t $(DOCKER_IMAGE):latest \
|
|
||||||
-t $(DOCKER_IMAGE):$(VERSION) \
|
|
||||||
--push \
|
|
||||||
--network=host \
|
|
||||||
.
|
|
||||||
|
|
||||||
update_android_version:
|
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:assembleOtherRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./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/other/release/*.apk 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/otherLegacy/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*-universal.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
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android
|
release_android: lib_android update_android_version build_android upload_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||||
@@ -126,28 +102,18 @@ 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 | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_ios_app_store:
|
upload_ios_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
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 | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_macos_app_store:
|
upload_macos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
@@ -156,82 +122,59 @@ 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:
|
||||||
$(MAKE) -C ../sing-box-for-apple archive_macos_standalone
|
cd ../sing-box-for-apple && \
|
||||||
|
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:
|
||||||
$(MAKE) -C ../sing-box-for-apple build_macos_dmg
|
rm -rf dist/SFM
|
||||||
|
mkdir -p dist/SFM
|
||||||
build_macos_pkg:
|
cd ../sing-box-for-apple && \
|
||||||
$(MAKE) -C ../sing-box-for-apple build_macos_pkg
|
rm -rf build/SFM.System && \
|
||||||
|
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:
|
||||||
$(MAKE) -C ../sing-box-for-apple notarize_macos_dmg
|
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||||
|
--keychain-profile "notarytool-password" \
|
||||||
notarize_macos_pkg:
|
--no-s3-acceleration
|
||||||
$(MAKE) -C ../sing-box-for-apple notarize_macos_pkg
|
|
||||||
|
|
||||||
upload_macos_dmg:
|
upload_macos_dmg:
|
||||||
mkdir -p dist/SFM
|
cd dist/SFM && \
|
||||||
cp ../sing-box-for-apple/build/SFM-Apple.dmg "dist/SFM/SFM-${VERSION}-Apple.dmg"
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
cp ../sing-box-for-apple/build/SFM-Intel.dmg "dist/SFM/SFM-${VERSION}-Intel.dmg"
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.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"
|
|
||||||
|
|
||||||
replace_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 "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
|
|
||||||
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
|
|
||||||
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
|
|
||||||
|
|
||||||
upload_macos_dsyms:
|
upload_macos_dsyms:
|
||||||
mkdir -p dist/SFM
|
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
||||||
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs && \
|
||||||
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
popd && \
|
||||||
|
cd dist/SFM && \
|
||||||
|
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||||
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
|
|
||||||
replace_macos_dsyms:
|
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||||
mkdir -p dist/SFM
|
|
||||||
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
|
|
||||||
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
|
||||||
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
|
|
||||||
|
|
||||||
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
|
|
||||||
|
|
||||||
replace_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 | xcbeautify | grep -A 10 -e "Archive Succeeded" -e "ARCHIVE FAILED" -e "❌"
|
xcodebuild archive -scheme SFT -configuration Release -archivePath build/SFT.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
upload_tvos_app_store:
|
upload_tvos_app_store:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@@ -240,12 +183,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_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone
|
release_apple: lib_ios 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 $(filter-out $@,$(MAKECMDGOALS))
|
go run -v ./cmd/internal/app_store_connect publish_testflight
|
||||||
|
|
||||||
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
|
||||||
@@ -268,21 +211,22 @@ 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_windows:
|
lib_ios:
|
||||||
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp
|
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
|
||||||
|
|
||||||
lib_android_new:
|
lib:
|
||||||
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android
|
go run ./cmd/internal/build_libbox -target 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.12
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -291,8 +235,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:
|
||||||
python3 -m venv venv
|
python -m venv venv
|
||||||
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*"
|
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
@@ -302,6 +246,3 @@ update:
|
|||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|
||||||
%:
|
|
||||||
@:
|
|
||||||
|
|||||||
85
README.md
85
README.md
@@ -1,87 +1,12 @@
|
|||||||
# sing-box-extended
|
# sing-box
|
||||||
|
|
||||||
Sing-box with extended features.
|
The universal proxy platform.
|
||||||
|
|
||||||
## 🔥 Features
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
### Outbounds
|
## Documentation
|
||||||
- **WARP** — Cloudflare WARP integration through WireGuard
|
|
||||||
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
|
|
||||||
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
|
|
||||||
- **Mieru** — Secure, hard to classify, hard to probe network protocol
|
|
||||||
- **VPN** — Routed tunnel over any TCP sing-box protocol
|
|
||||||
- **Bond** — Link aggregation for increasing throughput
|
|
||||||
- **Fallback** — Outbound group with priority-based switching
|
|
||||||
- **Failover** — Automatic outbound switching with session recovery for high availability
|
|
||||||
|
|
||||||
### DNS
|
https://sing-box.sagernet.org
|
||||||
- **SDNS (DNSCrypt)** — Encrypted DNS queries for enhanced privacy
|
|
||||||
- **DNS Fallback** — Sequential / parallel queries across upstream resolvers
|
|
||||||
|
|
||||||
### Limiters
|
|
||||||
- **Bandwidth Limiter** — Upload / download / bidirectional rate limiting
|
|
||||||
- **Connection Limiter** — Concurrent connection control
|
|
||||||
- **Traffic Limiter** — Per-user traffic quotas
|
|
||||||
- **Rate Limiter** — Request rate limiting
|
|
||||||
|
|
||||||
### Encryption & Obfuscation
|
|
||||||
- **Amnezia 2.0** — WireGuard traffic obfuscation
|
|
||||||
- **VLESS encryption** — XRAY encryption for VLESS protocol
|
|
||||||
|
|
||||||
### Transports
|
|
||||||
- **mKCP** — Reliable UDP-based transport
|
|
||||||
- **XHTTP** — Modern XRAY transport
|
|
||||||
|
|
||||||
### Services
|
|
||||||
- **Admin Panel** — Web-based management interface
|
|
||||||
- **Manager** — Management service for configuring users, nodes, limiters
|
|
||||||
- **Manager API (HTTP/gRPC)** — HTTP and gRPC API for the Manager
|
|
||||||
- **Node Manager API** — Service for connecting nodes to remote manager
|
|
||||||
|
|
||||||
### Miscellaneous
|
|
||||||
- **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
|
|
||||||
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC)
|
|
||||||
- **Extended WireGuard options** — Advanced configuration capabilities
|
|
||||||
- **Unified Delay** — Unified latency measurement
|
|
||||||
|
|
||||||
## 📚 Examples
|
|
||||||
|
|
||||||
Configuration examples are available here:
|
|
||||||
|
|
||||||
https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
|
|
||||||
|
|
||||||
## Support the Project
|
|
||||||
|
|
||||||
If you want to support the project, you can donate to the following addresses.
|
|
||||||
|
|
||||||
#### Tribute
|
|
||||||
|
|
||||||
**[RUB Donate](https://web.tribute.tg/d/JxY)**
|
|
||||||
|
|
||||||
**[EUR Donate](https://web.tribute.tg/d/JxZ)**
|
|
||||||
|
|
||||||
**[USD Donate](https://web.tribute.tg/d/Jy1)**
|
|
||||||
|
|
||||||
#### TRX (Tron)
|
|
||||||
```
|
|
||||||
TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F
|
|
||||||
```
|
|
||||||
#### TON
|
|
||||||
```
|
|
||||||
UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1
|
|
||||||
```
|
|
||||||
#### Solana
|
|
||||||
```
|
|
||||||
CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL
|
|
||||||
```
|
|
||||||
#### Bitcoin
|
|
||||||
```
|
|
||||||
bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x
|
|
||||||
```
|
|
||||||
#### Ethereum
|
|
||||||
```
|
|
||||||
0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ 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,6 +27,8 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +70,6 @@ type DNSTransport interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
// Reset closes the transport's existing connections so later requests use fresh connections.
|
|
||||||
// Exchanges that are currently using those connections may fail.
|
|
||||||
Reset()
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
@@ -42,18 +43,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.stage = stage
|
m.stage = stage
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
// started with outbound manager
|
// started with outbound manager
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
endpoints := m.endpoints
|
for _, endpoint := range m.endpoints {
|
||||||
m.access.Unlock()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -71,14 +66,11 @@ 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 {
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
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 ", name)
|
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -127,13 +119,10 @@ 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 {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(endpoint, stage)
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +14,6 @@ type ClashServer interface {
|
|||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
SetModeUpdateHook(hook *observable.Subscriber[struct{}])
|
|
||||||
HistoryStorage() URLTestHistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ type URLTestHistory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type URLTestHistoryStorage interface {
|
type URLTestHistoryStorage interface {
|
||||||
SetHook(hook *observable.Subscriber[struct{}])
|
SetHook(hook chan<- 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)
|
||||||
@@ -47,9 +44,6 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreWARPConfig() bool
|
|
||||||
StoreMASQUEConfig() bool
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
@@ -58,12 +52,6 @@ type CacheFile interface {
|
|||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
LoadWARPConfig(tag string) *SavedBinary
|
|
||||||
SaveWARPConfig(tag string, set *SavedBinary) error
|
|
||||||
LoadMASQUEConfig(tag string) *SavedBinary
|
|
||||||
SaveMASQUEConfig(tag string, set *SavedBinary) error
|
|
||||||
LoadSubscription(tag string) *SavedBinary
|
|
||||||
SaveSubscription(tag string, sub *SavedBinary) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedBinary struct {
|
||||||
@@ -78,11 +66,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))
|
err = varbin.Write(&buffer, binary.BigEndian, 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
|
||||||
}
|
}
|
||||||
@@ -90,11 +74,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))
|
err = varbin.Write(&buffer, binary.BigEndian, 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
|
||||||
}
|
}
|
||||||
@@ -108,12 +88,7 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentLength, err := binary.ReadUvarint(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.Content)
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -123,16 +98,10 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.LastUpdated = time.Unix(lastUpdated, 0)
|
s.LastUpdated = time.Unix(lastUpdated, 0)
|
||||||
etagLength, err := binary.ReadUvarint(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"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"
|
||||||
@@ -30,7 +31,6 @@ type UDPInjectableInbound interface {
|
|||||||
type InboundRegistry interface {
|
type InboundRegistry interface {
|
||||||
option.InboundOptionsRegistry
|
option.InboundOptionsRegistry
|
||||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||||
UnsafeCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundManager interface {
|
type InboundManager interface {
|
||||||
@@ -48,26 +48,27 @@ type InboundContext struct {
|
|||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
Gateway *netip.Addr
|
|
||||||
User string
|
User string
|
||||||
Outbound string
|
Outbound string
|
||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SnifferNames []string
|
PacketSniffError error
|
||||||
SniffError error
|
|
||||||
|
|
||||||
// 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
|
||||||
@@ -83,7 +84,7 @@ type InboundContext struct {
|
|||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *ConnectionOwner
|
ProcessInfo *process.Info
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
@@ -103,10 +104,6 @@ type InboundContext struct {
|
|||||||
func (c *InboundContext) ResetRuleCache() {
|
func (c *InboundContext) ResetRuleCache() {
|
||||||
c.IPCIDRMatchSource = false
|
c.IPCIDRMatchSource = false
|
||||||
c.IPCIDRAcceptEmpty = false
|
c.IPCIDRAcceptEmpty = false
|
||||||
c.ResetRuleMatchCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InboundContext) ResetRuleMatchCache() {
|
|
||||||
c.SourceAddressMatch = false
|
c.SourceAddressMatch = false
|
||||||
c.SourcePortMatch = false
|
c.SourcePortMatch = false
|
||||||
c.DestinationAddressMatch = false
|
c.DestinationAddressMatch = false
|
||||||
@@ -138,7 +135,8 @@ func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
|||||||
|
|
||||||
func OverrideContext(ctx context.Context) context.Context {
|
func OverrideContext(ctx context.Context) context.Context {
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
newMetadata := *metadata
|
var newMetadata InboundContext
|
||||||
|
newMetadata = *metadata
|
||||||
return WithContext(ctx, &newMetadata)
|
return WithContext(ctx, &newMetadata)
|
||||||
}
|
}
|
||||||
return ctx
|
return ctx
|
||||||
|
|||||||
@@ -45,12 +45,9 @@ 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() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -68,14 +65,11 @@ 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 {
|
||||||
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
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 ", name)
|
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -127,13 +121,10 @@ 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 {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(inbound, stage)
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
|||||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
return m.UnsafeCreate(ctx, router, logger, tag, outboundType, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) UnsafeCreate(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
|
||||||
constructor, loaded := m.constructor[outboundType]
|
constructor, loaded := m.constructor[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
"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
|
||||||
@@ -56,30 +48,9 @@ type LifecycleService interface {
|
|||||||
Lifecycle
|
Lifecycle
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServiceName(service any) string {
|
func Start(stage StartStage, services ...Lifecycle) error {
|
||||||
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)
|
|
||||||
done := LogElapsed(logger, stage, " ", name)
|
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,28 +58,12 @@ func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
|
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
done := LogElapsed(logger, stage, " ", service.Name())
|
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage.String(), " ", service.Name())
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogElapsed(logger log.ContextLogger, description ...any) func() {
|
|
||||||
prefix := F.ToString(description...)
|
|
||||||
startTime := time.Now()
|
|
||||||
timer := time.AfterFunc(time.Second, func() {
|
|
||||||
logger.Trace(prefix, "...")
|
|
||||||
})
|
|
||||||
return func() {
|
|
||||||
if timer.Stop() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Trace(prefix, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -13,7 +10,6 @@ import (
|
|||||||
|
|
||||||
type NetworkManager interface {
|
type NetworkManager interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
Initialize(ruleSets []RuleSet)
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
UpdateInterfaces() error
|
||||||
DefaultNetworkInterface() *NetworkInterface
|
DefaultNetworkInterface() *NetworkInterface
|
||||||
@@ -24,14 +20,12 @@ type NetworkManager interface {
|
|||||||
DefaultOptions() NetworkOptions
|
DefaultOptions() NetworkOptions
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
AutoRedirectOutputMark() uint32
|
AutoRedirectOutputMark() uint32
|
||||||
AutoRedirectOutputMarkFunc() control.Func
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
NeedWIFIState() bool
|
|
||||||
WIFIState() WIFIState
|
WIFIState() WIFIState
|
||||||
UpdateWIFIState()
|
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
|
UpdateWIFIState()
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkOptions struct {
|
type NetworkOptions struct {
|
||||||
@@ -54,24 +48,6 @@ type WIFIState struct {
|
|||||||
BSSID string
|
BSSID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NormalizeWIFIBSSID(bssid string) string {
|
|
||||||
bssid = strings.TrimSpace(bssid)
|
|
||||||
if bssid == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
parsed, err := net.ParseMAC(bssid)
|
|
||||||
if err == nil && len(parsed) == 6 {
|
|
||||||
return parsed.String()
|
|
||||||
}
|
|
||||||
if len(bssid) == 12 {
|
|
||||||
decoded, err := hex.DecodeString(bssid)
|
|
||||||
if err == nil {
|
|
||||||
return net.HardwareAddr(decoded).String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bssid
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkInterface struct {
|
type NetworkInterface struct {
|
||||||
control.Interface
|
control.Interface
|
||||||
Type C.InterfaceType
|
Type C.InterfaceType
|
||||||
|
|||||||
@@ -2,12 +2,9 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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-tun"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,21 +18,9 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundWithPreferredRoutes interface {
|
|
||||||
Outbound
|
|
||||||
PreferredDomain(domain string) bool
|
|
||||||
PreferredAddress(address netip.Addr) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirectRouteOutbound interface {
|
|
||||||
Outbound
|
|
||||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
UnsafeCreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundManager interface {
|
type OutboundManager interface {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type Manager struct {
|
|||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultOutbound adapter.Outbound
|
defaultOutbound adapter.Outbound
|
||||||
defaultOutboundFallback func() (adapter.Outbound, error)
|
defaultOutboundFallback adapter.Outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||||
@@ -44,7 +44,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
|
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||||
m.defaultOutboundFallback = defaultOutboundFallback
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,38 +55,22 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
m.access.Unlock()
|
|
||||||
return E.New("default outbound not found: ", m.defaultTag)
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
m.defaultOutbound = defaultEndpoint
|
m.defaultOutbound = defaultEndpoint
|
||||||
}
|
}
|
||||||
if m.defaultOutbound == nil {
|
|
||||||
directOutbound, err := m.defaultOutboundFallback()
|
|
||||||
if err != nil {
|
|
||||||
m.access.Unlock()
|
|
||||||
return E.Cause(err, "create direct outbound for fallback")
|
|
||||||
}
|
|
||||||
m.outbounds = append(m.outbounds, directOutbound)
|
|
||||||
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
|
||||||
m.defaultOutbound = directOutbound
|
|
||||||
}
|
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
} else {
|
} else {
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,26 +96,21 @@ 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 {
|
||||||
done := adapter.LogElapsed(m.logger, "start ", name)
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
monitor.Start("start ", name)
|
|
||||||
err := starter.Start(adapter.StartStateStart)
|
err := starter.Start(adapter.StartStateStart)
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", name)
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
}
|
}
|
||||||
} else if starter, isStarter := outboundToStart.(interface {
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
Start() error
|
Start() error
|
||||||
}); isStarter {
|
}); isStarter {
|
||||||
done := adapter.LogElapsed(m.logger, "start ", name)
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
monitor.Start("start ", name)
|
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", name)
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,14 +158,11 @@ 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 {
|
||||||
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
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 ", name)
|
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -211,7 +187,11 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
func (m *Manager) Default() adapter.Outbound {
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
return m.defaultOutbound
|
if m.defaultOutbound != nil {
|
||||||
|
return m.defaultOutbound
|
||||||
|
} else {
|
||||||
|
return m.defaultOutboundFallback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
func (m *Manager) Remove(tag string) error {
|
||||||
@@ -267,13 +247,10 @@ 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 {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(outbound, stage)
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
|||||||
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
r.access.Lock()
|
r.access.Lock()
|
||||||
defer r.access.Unlock()
|
defer r.access.Unlock()
|
||||||
return r.UnsafeCreateOutbound(ctx, router, logger, tag, outboundType, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) UnsafeCreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
|
||||||
constructor, loaded := r.constructors[outboundType]
|
constructor, loaded := r.constructors[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"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
|
|
||||||
|
|
||||||
MyInterfaceAddress() []netip.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
type FindConnectionOwnerRequest struct {
|
|
||||||
IpProtocol int32
|
|
||||||
SourceAddress string
|
|
||||||
SourcePort int32
|
|
||||||
DestinationAddress string
|
|
||||||
DestinationPort int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionOwner struct {
|
|
||||||
ProcessID uint32
|
|
||||||
UserId int32
|
|
||||||
UserName string
|
|
||||||
ProcessPath string
|
|
||||||
AndroidPackageNames []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
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider interface {
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
Outbounds() []Outbound
|
|
||||||
Outbound(tag string) (Outbound, bool)
|
|
||||||
UpdatedAt() time.Time
|
|
||||||
HealthCheck(ctx context.Context) (map[string]uint16, error)
|
|
||||||
RegisterCallback(callback ProviderUpdateCallback) *list.Element[ProviderUpdateCallback]
|
|
||||||
UnregisterCallback(element *list.Element[ProviderUpdateCallback])
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderUpdater interface {
|
|
||||||
Update() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderSubscriptionInfo interface {
|
|
||||||
SubscriptionInfo() SubscriptionInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderRegistry interface {
|
|
||||||
option.ProviderOptionsRegistry
|
|
||||||
CreateProvider(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) (Provider, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Providers() []Provider
|
|
||||||
Get(tag string) (Provider, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscriptionInfo struct {
|
|
||||||
Upload int64
|
|
||||||
Download int64
|
|
||||||
Total int64
|
|
||||||
Expire int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProviderUpdateCallback = func(tag string) error
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/batch"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
ctx context.Context
|
|
||||||
outbound adapter.OutboundManager
|
|
||||||
router adapter.Router
|
|
||||||
logFactory log.Factory
|
|
||||||
logger log.ContextLogger
|
|
||||||
providerType string
|
|
||||||
providerTag string
|
|
||||||
outbounds []adapter.Outbound
|
|
||||||
outboundsByTag map[string]adapter.Outbound
|
|
||||||
ticker *time.Ticker
|
|
||||||
checking atomic.Bool
|
|
||||||
history adapter.URLTestHistoryStorage
|
|
||||||
callbackAccess sync.Mutex
|
|
||||||
callbacks list.List[adapter.ProviderUpdateCallback]
|
|
||||||
|
|
||||||
link string
|
|
||||||
enabled bool
|
|
||||||
timeout time.Duration
|
|
||||||
interval time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
|
|
||||||
timeout := time.Duration(options.Timeout)
|
|
||||||
if timeout == 0 {
|
|
||||||
timeout = 3 * time.Second
|
|
||||||
}
|
|
||||||
interval := time.Duration(options.Interval)
|
|
||||||
if interval == 0 {
|
|
||||||
interval = 10 * time.Minute
|
|
||||||
}
|
|
||||||
if interval < time.Minute {
|
|
||||||
interval = time.Minute
|
|
||||||
}
|
|
||||||
return Adapter{
|
|
||||||
ctx: ctx,
|
|
||||||
outbound: outbound,
|
|
||||||
router: router,
|
|
||||||
logFactory: logFactory,
|
|
||||||
logger: logger,
|
|
||||||
providerType: providerType,
|
|
||||||
providerTag: providerTag,
|
|
||||||
|
|
||||||
enabled: options.Enabled,
|
|
||||||
link: options.URL,
|
|
||||||
timeout: timeout,
|
|
||||||
interval: interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Start() error {
|
|
||||||
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
|
||||||
if a.history == nil {
|
|
||||||
if clashServer := service.FromContext[adapter.ClashServer](a.ctx); clashServer != nil {
|
|
||||||
a.history = clashServer.HistoryStorage()
|
|
||||||
} else {
|
|
||||||
a.history = urltest.NewHistoryStorage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go a.loopCheck()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.providerType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.providerTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Outbounds() []adapter.Outbound {
|
|
||||||
return a.outbounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
|
|
||||||
if a.outboundsByTag == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
detour, ok := a.outboundsByTag[tag]
|
|
||||||
return detour, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
|
||||||
a.removeUseless(newOpts)
|
|
||||||
var (
|
|
||||||
oldOptByTag = make(map[string]option.Outbound)
|
|
||||||
outbounds = make([]adapter.Outbound, 0, len(newOpts))
|
|
||||||
outboundsByTag = make(map[string]adapter.Outbound)
|
|
||||||
)
|
|
||||||
for _, opt := range oldOpts {
|
|
||||||
oldOptByTag[opt.Tag] = opt
|
|
||||||
}
|
|
||||||
for i, opt := range newOpts {
|
|
||||||
var tag string
|
|
||||||
if opt.Tag != "" {
|
|
||||||
tag = F.ToString(a.providerTag, "/", opt.Tag)
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(a.providerTag, "/", i)
|
|
||||||
}
|
|
||||||
outbound, exist := a.outbound.Outbound(tag)
|
|
||||||
if !exist || !reflect.DeepEqual(opt, oldOptByTag[opt.Tag]) {
|
|
||||||
err := a.outbound.Create(
|
|
||||||
adapter.WithContext(a.ctx, &adapter.InboundContext{
|
|
||||||
Outbound: tag,
|
|
||||||
}),
|
|
||||||
a.router,
|
|
||||||
a.logFactory.NewLogger(F.ToString("outbound/", opt.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
opt.Type,
|
|
||||||
opt.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Warn(err, " in ", tag, ", skip create this outbound")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
outbound, _ = a.outbound.Outbound(tag)
|
|
||||||
}
|
|
||||||
outbounds = append(outbounds, outbound)
|
|
||||||
outboundsByTag[tag] = outbound
|
|
||||||
}
|
|
||||||
if a.enabled && a.history != nil {
|
|
||||||
go a.HealthCheck(a.ctx)
|
|
||||||
}
|
|
||||||
a.outbounds = outbounds
|
|
||||||
a.outboundsByTag = outboundsByTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) HealthCheck(ctx context.Context) (map[string]uint16, error) {
|
|
||||||
if a.ticker != nil {
|
|
||||||
a.ticker.Reset(a.interval)
|
|
||||||
}
|
|
||||||
return a.healthcheck(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) RegisterCallback(callback adapter.ProviderUpdateCallback) *list.Element[adapter.ProviderUpdateCallback] {
|
|
||||||
a.callbackAccess.Lock()
|
|
||||||
defer a.callbackAccess.Unlock()
|
|
||||||
return a.callbacks.PushBack(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) UnregisterCallback(element *list.Element[adapter.ProviderUpdateCallback]) {
|
|
||||||
a.callbackAccess.Lock()
|
|
||||||
defer a.callbackAccess.Unlock()
|
|
||||||
a.callbacks.Remove(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) UpdateGroups() {
|
|
||||||
for element := a.callbacks.Front(); element != nil; element = element.Next() {
|
|
||||||
element.Value(a.providerTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Close() error {
|
|
||||||
if a.ticker != nil {
|
|
||||||
a.ticker.Stop()
|
|
||||||
}
|
|
||||||
outbounds := a.outbounds
|
|
||||||
a.outbounds = nil
|
|
||||||
var err error
|
|
||||||
for _, ob := range outbounds {
|
|
||||||
if err2 := a.outbound.Remove(ob.Tag()); err2 != nil {
|
|
||||||
err = E.Append(err, err2, func(err error) error {
|
|
||||||
return E.Cause(err, "close outbound [", ob.Tag(), "]")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) loopCheck() {
|
|
||||||
if !a.enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.ticker = time.NewTicker(a.interval)
|
|
||||||
a.healthcheck(a.ctx)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-a.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-a.ticker.C:
|
|
||||||
a.healthcheck(a.ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) healthcheck(ctx context.Context) (map[string]uint16, error) {
|
|
||||||
result := make(map[string]uint16)
|
|
||||||
if a.checking.Swap(true) {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
defer a.checking.Store(false)
|
|
||||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
|
||||||
var resultAccess sync.Mutex
|
|
||||||
checked := make(map[string]bool)
|
|
||||||
for _, detour := range a.outbounds {
|
|
||||||
tag := detour.Tag()
|
|
||||||
if checked[tag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
checked[tag] = true
|
|
||||||
b.Go(tag, func() (any, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(a.ctx, a.timeout)
|
|
||||||
defer cancel()
|
|
||||||
t, err := urltest.URLTest(ctx, a.link, detour)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Debug("outbound ", tag, " unavailable: ", err)
|
|
||||||
a.history.DeleteURLTestHistory(tag)
|
|
||||||
} else {
|
|
||||||
a.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
|
||||||
a.history.StoreURLTestHistory(tag, &adapter.URLTestHistory{
|
|
||||||
Time: time.Now(),
|
|
||||||
Delay: t,
|
|
||||||
})
|
|
||||||
resultAccess.Lock()
|
|
||||||
result[tag] = t
|
|
||||||
resultAccess.Unlock()
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
b.Wait()
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) removeUseless(newOpts []option.Outbound) {
|
|
||||||
if len(a.outbounds) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
exists := make(map[string]bool)
|
|
||||||
for i, opt := range newOpts {
|
|
||||||
var tag string
|
|
||||||
if opt.Tag != "" {
|
|
||||||
tag = F.ToString(a.providerTag, "/", opt.Tag)
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(a.providerTag, "/", i)
|
|
||||||
}
|
|
||||||
exists[tag] = true
|
|
||||||
}
|
|
||||||
for _, opt := range a.outbounds {
|
|
||||||
if !exists[opt.Tag()] {
|
|
||||||
if err := a.outbound.Remove(opt.Tag()); err != nil {
|
|
||||||
a.logger.Error(err, "close outbound [", opt.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.ProviderManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.ProviderRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
providers []adapter.Provider
|
|
||||||
providerByTag map[string]adapter.Provider
|
|
||||||
wg sync.WaitGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.ProviderRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
providerByTag: make(map[string]adapter.Provider),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Initialize() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
providers := m.providers
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, provider := range providers {
|
|
||||||
err := adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
m.access.Lock()
|
|
||||||
if !m.started {
|
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
providers := m.providers
|
|
||||||
m.providers = nil
|
|
||||||
m.access.Unlock()
|
|
||||||
var err error
|
|
||||||
for _, provider := range providers {
|
|
||||||
if closer, isCloser := provider.(io.Closer); isCloser {
|
|
||||||
monitor.Start("close provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Providers() []adapter.Provider {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.Provider, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return provider, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.providerByTag, tag)
|
|
||||||
index := common.Index(m.providers, func(it adapter.Provider) bool {
|
|
||||||
return it == provider
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:index], m.providers[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return common.Close(provider)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) error {
|
|
||||||
if tag == "" {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := m.registry.CreateProvider(ctx, router, logFactory, tag, providerType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
err = adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsProvider, loaded := m.providerByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = common.Close(existsProvider)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close provider", provider.Type(), "[", existsProvider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.providers, func(it adapter.Provider) bool {
|
|
||||||
return it == existsProvider
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers, provider)
|
|
||||||
m.providerByTag[tag] = provider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options T) (adapter.Provider, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(providerType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, rawOptions any) (adapter.Provider, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, router, logFactory, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.ProviderRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options any) (adapter.Provider, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructors map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructors: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) CreateOptions(providerType string) (any, bool) {
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
optionsConstructor, loaded := r.optionsType[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) CreateProvider(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) (adapter.Provider, error) {
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
constructor, loaded := r.constructors[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("provider type not found: '" + providerType + "'")
|
|
||||||
}
|
|
||||||
return constructor(ctx, router, logFactory, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
r.access.Lock()
|
|
||||||
defer r.access.Unlock()
|
|
||||||
r.optionsType[providerType] = optionsConstructor
|
|
||||||
r.constructors[providerType] = constructor
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
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"
|
||||||
@@ -21,11 +19,11 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration, supportBypass bool) (tun.DirectRouteDestination, error)
|
PreMatch(metadata InboundContext) error
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
NeedWIFIState() bool
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
NeedFindProcess() bool
|
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,12 +43,9 @@ 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() + "]"
|
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err := adapter.LegacyStart(service, stage)
|
err := adapter.LegacyStart(service, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -66,14 +63,11 @@ 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 {
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
done := adapter.LogElapsed(m.logger, "close ", name)
|
|
||||||
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 ", name)
|
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -122,13 +116,10 @@ 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 {
|
||||||
done := adapter.LogElapsed(m.logger, stage, " ", name)
|
|
||||||
err = adapter.LegacyStart(service, stage)
|
err = adapter.LegacyStart(service, stage)
|
||||||
done()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func NewUpstreamContextHandlerEx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, myMetadata := ExtendContext(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, myMetadata := ExtendContext(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ type routeContextHandlerWrapperEx struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, metadata := ExtendContext(ctx)
|
metadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
_, metadata := ExtendContext(ctx)
|
metadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if source.IsValid() {
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
|||||||
// Deprecated: removed
|
// Deprecated: removed
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
return M.Metadata{
|
return M.Metadata{
|
||||||
Source: metadata.Source.Unwrap(),
|
Source: metadata.Source,
|
||||||
Destination: metadata.Destination.Unwrap(),
|
Destination: metadata.Destination,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
127
box.go
127
box.go
@@ -12,17 +12,17 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
"github.com/sagernet/sing-box/adapter/provider"
|
|
||||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
"github.com/sagernet/sing-box/common/certificate"
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"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"
|
||||||
@@ -45,7 +45,6 @@ type Box struct {
|
|||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
provider *provider.Manager
|
|
||||||
service *boxService.Manager
|
service *boxService.Manager
|
||||||
dnsTransport *dns.TransportManager
|
dnsTransport *dns.TransportManager
|
||||||
dnsRouter *dns.Router
|
dnsRouter *dns.Router
|
||||||
@@ -66,7 +65,6 @@ func Context(
|
|||||||
inboundRegistry adapter.InboundRegistry,
|
inboundRegistry adapter.InboundRegistry,
|
||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
providerRegistry adapter.ProviderRegistry,
|
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
) context.Context {
|
) context.Context {
|
||||||
@@ -85,11 +83,6 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
}
|
}
|
||||||
if service.FromContext[option.ProviderOptionsRegistry](ctx) == nil ||
|
|
||||||
service.FromContext[adapter.ProviderRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.ProviderOptionsRegistry](ctx, providerRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.ProviderRegistry](ctx, providerRegistry)
|
|
||||||
}
|
|
||||||
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
@@ -112,7 +105,6 @@ func New(options Options) (*Box, error) {
|
|||||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
providerRegistry := service.FromContext[adapter.ProviderRegistry](ctx)
|
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
|
|
||||||
@@ -125,9 +117,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
if providerRegistry == nil {
|
|
||||||
return nil, E.New("missing provider registry in context")
|
|
||||||
}
|
|
||||||
if dnsTransportRegistry == nil {
|
if dnsTransportRegistry == nil {
|
||||||
return nil, E.New("missing DNS transport registry in context")
|
return nil, E.New("missing DNS transport registry in context")
|
||||||
}
|
}
|
||||||
@@ -137,10 +126,7 @@ func New(options Options) (*Box, error) {
|
|||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
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
|
||||||
@@ -153,10 +139,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
|
||||||
}
|
}
|
||||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
ctx = urltest.ContextWithIsUnifiedDelay(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
|
||||||
@@ -172,7 +155,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
service.MustRegister[log.Factory](ctx, logFactory)
|
|
||||||
|
|
||||||
var internalServices []adapter.LifecycleService
|
var internalServices []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
@@ -193,18 +175,16 @@ func New(options Options) (*Box, error) {
|
|||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
providerManager := provider.NewManager(logFactory.NewLogger("provider"), providerRegistry)
|
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.ProviderManager](ctx, providerManager)
|
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
}
|
}
|
||||||
@@ -290,10 +270,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.Outbounds = append(options.Outbounds, option.Outbound{
|
|
||||||
Tag: "Compatible",
|
|
||||||
Type: C.TypeDirect,
|
|
||||||
})
|
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
@@ -320,25 +296,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, providerOptions := range options.Providers {
|
|
||||||
var tag string
|
|
||||||
if providerOptions.Tag != "" {
|
|
||||||
tag = providerOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = providerManager.Create(
|
|
||||||
ctx,
|
|
||||||
router,
|
|
||||||
logFactory,
|
|
||||||
tag,
|
|
||||||
providerOptions.Type,
|
|
||||||
providerOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize provider[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, serviceOptions := range options.Services {
|
for i, serviceOptions := range options.Services {
|
||||||
var tag string
|
var tag string
|
||||||
if serviceOptions.Tag != "" {
|
if serviceOptions.Tag != "" {
|
||||||
@@ -357,24 +314,22 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
outboundManager.Initialize(common.Must1(
|
||||||
return direct.NewOutbound(
|
direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger("outbound/direct"),
|
logFactory.NewLogger("outbound/direct"),
|
||||||
"direct",
|
"direct",
|
||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
)
|
),
|
||||||
})
|
))
|
||||||
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
return dnsTransportRegistry.CreateDNSTransport(
|
local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
C.DNSTypeLocal,
|
option.LocalDNSServerOptions{},
|
||||||
&option.LocalDNSServerOptions{},
|
)))
|
||||||
)
|
|
||||||
})
|
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -429,7 +384,6 @@ func New(options Options) (*Box, error) {
|
|||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
provider: providerManager,
|
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
service: serviceManager,
|
service: serviceManager,
|
||||||
dnsRouter: dnsRouter,
|
dnsRouter: dnsRouter,
|
||||||
@@ -489,15 +443,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(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
|
err = adapter.Start(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(s.logger, adapter.StartStateStart, s.outbound, s.provider, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -509,27 +463,27 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService)
|
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.provider, s.service)
|
err = adapter.Start(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(s.logger, adapter.StartStatePostStart, s.internalService)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
|
err = adapter.Start(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(s.logger, adapter.StartStateStarted, s.internalService)
|
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -543,40 +497,17 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
var err error
|
err := common.Close(
|
||||||
for _, closeItem := range []struct {
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
name string
|
)
|
||||||
service adapter.Lifecycle
|
|
||||||
}{
|
|
||||||
{"service", s.service},
|
|
||||||
{"endpoint", s.endpoint},
|
|
||||||
{"inbound", s.inbound},
|
|
||||||
{"provider", s.provider},
|
|
||||||
{"outbound", s.outbound},
|
|
||||||
{"router", s.router},
|
|
||||||
{"connection", s.connection},
|
|
||||||
{"dns-router", s.dnsRouter},
|
|
||||||
{"dns-transport", s.dnsTransport},
|
|
||||||
{"network", s.network},
|
|
||||||
} {
|
|
||||||
done := adapter.LogElapsed(s.logger, "close ", closeItem.name)
|
|
||||||
err = E.Append(err, closeItem.service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", closeItem.name)
|
|
||||||
})
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.internalService {
|
||||||
done := adapter.LogElapsed(s.logger, "close ", lifecycleService.Name())
|
|
||||||
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())
|
||||||
})
|
})
|
||||||
done()
|
|
||||||
}
|
}
|
||||||
done := adapter.LogElapsed(s.logger, "close logger")
|
|
||||||
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")
|
||||||
})
|
})
|
||||||
done()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,11 +526,3 @@ 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) Endpoint() adapter.EndpointManager {
|
|
||||||
return s.endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) LogFactory() log.Factory {
|
|
||||||
return s.logFactory
|
|
||||||
}
|
|
||||||
|
|||||||
Submodule clients/android updated: eb87216961...cec05bf693
Submodule clients/apple updated: c19945f65b...ae5818ee5a
@@ -1,194 +0,0 @@
|
|||||||
// Command admin_panel_pack post-processes a directory of built SPA
|
|
||||||
// assets so it can be served straight from the Go binary via //go:embed.
|
|
||||||
// It does *not* generate any Go source any more — that responsibility
|
|
||||||
// moved to the embed directive in service/admin_panel/service.go.
|
|
||||||
//
|
|
||||||
// Three transformations happen here, all in-place inside the supplied
|
|
||||||
// directory:
|
|
||||||
//
|
|
||||||
// 1. Legacy WOFF (1.0) fallback fonts are deleted. Every browser made
|
|
||||||
// after 2014 reads WOFF2 natively, so shipping both formats roughly
|
|
||||||
// doubles the embedded font payload for no real-world benefit. The
|
|
||||||
// matching `,url(*.woff) format("woff")` segments are stripped from
|
|
||||||
// the bundled CSS in step (2) so the @font-face rules don't reference
|
|
||||||
// files that aren't shipped.
|
|
||||||
// 2. Bundled CSS is rewritten to drop those WOFF URL fragments.
|
|
||||||
// 3. Compressible text assets (.html, .css, .js, .svg, .json, .map) are
|
|
||||||
// pre-gzipped as companion `*.gz` files. The HTTP handler then either
|
|
||||||
// passes those bytes through verbatim with Content-Encoding: gzip or
|
|
||||||
// falls back to the raw file for the rare client that does not
|
|
||||||
// advertise gzip support — no on-line compression, no surprises.
|
|
||||||
// Already-compressed formats (.woff2, fonts, images) are skipped: gzip
|
|
||||||
// can't shrink them and the duplicate would only inflate the binary.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
dir := flag.String("dir", "service/admin_panel/dist", "directory of built SPA assets to post-process in place")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
woffDropped, err := pruneWoff(*dir)
|
|
||||||
if err != nil {
|
|
||||||
fail("prune woff: %v", err)
|
|
||||||
}
|
|
||||||
cssRewritten, err := rewriteCSS(*dir)
|
|
||||||
if err != nil {
|
|
||||||
fail("rewrite css: %v", err)
|
|
||||||
}
|
|
||||||
stats, err := gzipText(*dir)
|
|
||||||
if err != nil {
|
|
||||||
fail("gzip text: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "post-processed %s: dropped %d woff, rewrote %d css, gzipped %d files (%d→%d bytes)\n",
|
|
||||||
*dir, woffDropped, cssRewritten, stats.gzipped, stats.totalRaw, stats.totalGz)
|
|
||||||
}
|
|
||||||
|
|
||||||
// pruneWoff deletes every legacy *.woff (WOFF 1.0) font under dir. The
|
|
||||||
// bundled CSS still references them on entry; rewriteCSS drops those
|
|
||||||
// references in a separate pass so the two operations stay independently
|
|
||||||
// testable.
|
|
||||||
func pruneWoff(dir string) (int, error) {
|
|
||||||
var n int
|
|
||||||
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
|
|
||||||
if walkErr != nil {
|
|
||||||
return walkErr
|
|
||||||
}
|
|
||||||
if d.IsDir() || !strings.EqualFold(filepath.Ext(p), ".woff") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.Remove(p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// woffRefAfterRE / woffRefBeforeRE match a single WOFF 1.0 entry inside a
|
|
||||||
// Vite-bundled CSS `src:` declaration. Vite minifies the rule to
|
|
||||||
// `src:url(./X.woff2) format("woff2"),url(./X.woff) format("woff");` so the
|
|
||||||
// "after" regex (the common case) eats the *leading* comma + woff entry,
|
|
||||||
// leaving only the woff2 source. We also handle the rare reverse ordering
|
|
||||||
// in a second pass.
|
|
||||||
var (
|
|
||||||
woffRefAfterRE = regexp.MustCompile(`,\s*url\([^)]*\.woff\)\s*format\(["']woff["']\)`)
|
|
||||||
woffRefBeforeRE = regexp.MustCompile(`url\([^)]*\.woff\)\s*format\(["']woff["']\)\s*,\s*`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// rewriteCSS drops every reference to a *.woff URL from every *.css file
|
|
||||||
// under dir. Pairs naturally with pruneWoff: after both passes, no font
|
|
||||||
// URL in the bundle points at a file that isn't shipped.
|
|
||||||
func rewriteCSS(dir string) (int, error) {
|
|
||||||
var n int
|
|
||||||
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
|
|
||||||
if walkErr != nil {
|
|
||||||
return walkErr
|
|
||||||
}
|
|
||||||
if d.IsDir() || !strings.EqualFold(filepath.Ext(p), ".css") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data, err := os.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out := woffRefAfterRE.ReplaceAll(data, nil)
|
|
||||||
out = woffRefBeforeRE.ReplaceAll(out, nil)
|
|
||||||
if bytes.Equal(out, data) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.WriteFile(p, out, 0o644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// gzipExts is the set of file extensions for which a `.gz` companion is
|
|
||||||
// generated. Anything not on this list is left alone — woff2/png/jpeg/etc.
|
|
||||||
// are already compressed, so gzip can only inflate them slightly while
|
|
||||||
// doubling the embedded payload.
|
|
||||||
var gzipExts = map[string]bool{
|
|
||||||
".html": true,
|
|
||||||
".css": true,
|
|
||||||
".js": true,
|
|
||||||
".mjs": true,
|
|
||||||
".svg": true,
|
|
||||||
".json": true,
|
|
||||||
".map": true,
|
|
||||||
".txt": true,
|
|
||||||
".xml": true,
|
|
||||||
".wasm": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
type gzipStats struct {
|
|
||||||
gzipped int
|
|
||||||
totalRaw int64
|
|
||||||
totalGz int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// gzipText produces a `<file>.gz` companion next to every text-like asset
|
|
||||||
// in dir, using gzip.BestCompression. The companion is dropped if the
|
|
||||||
// compressed bytes don't save at least 10 % over the raw file — same
|
|
||||||
// heuristic we used in the previous (Go-source-emitting) generation, just
|
|
||||||
// applied to disk files now.
|
|
||||||
func gzipText(dir string) (gzipStats, error) {
|
|
||||||
var stats gzipStats
|
|
||||||
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
|
|
||||||
if walkErr != nil {
|
|
||||||
return walkErr
|
|
||||||
}
|
|
||||||
if d.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ext := strings.ToLower(filepath.Ext(p))
|
|
||||||
if ext == ".gz" || !gzipExts[ext] {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
raw, err := os.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Reproducible: no mtime, no OS marker.
|
|
||||||
w.ModTime = time.Time{}
|
|
||||||
w.OS = 0xff
|
|
||||||
if _, err := w.Write(raw); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if buf.Len() > len(raw)*9/10 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
stats.gzipped++
|
|
||||||
stats.totalRaw += int64(len(raw))
|
|
||||||
stats.totalGz += int64(buf.Len())
|
|
||||||
return os.WriteFile(p+".gz", buf.Bytes(), 0o644)
|
|
||||||
})
|
|
||||||
return stats, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func fail(format string, args ...any) {
|
|
||||||
fmt.Fprintf(os.Stderr, "admin_panel_pack: "+format+"\n", args...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
@@ -100,33 +100,12 @@ 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()
|
||||||
|
client := createClient(10 * time.Minute)
|
||||||
releaseNotes := F.ToString("sing-box ", tagVersion.String())
|
|
||||||
if len(os.Args) >= 4 {
|
|
||||||
releaseNotes = strings.Join(os.Args[3:], " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := createClient(20 * time.Minute)
|
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -136,76 +115,95 @@ 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
|
||||||
waitingForProcess := false
|
if len(os.Args) == 3 {
|
||||||
log.Info(string(platform), " list builds")
|
switch os.Args[2] {
|
||||||
for {
|
case "ios":
|
||||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
platforms = []asc.Platform{asc.PlatformIOS}
|
||||||
FilterApp: []string{appID},
|
case "macos":
|
||||||
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||||
})
|
case "tvos":
|
||||||
if err != nil {
|
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||||
return err
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
} else {
|
||||||
log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")")
|
platforms = []asc.Platform{
|
||||||
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
asc.PlatformIOS,
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
asc.PlatformMACOS,
|
||||||
time.Sleep(15 * time.Second)
|
asc.PlatformTVOS,
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if *build.Attributes.ProcessingState != "VALID" {
|
}
|
||||||
waitingForProcess = true
|
for _, platform := range platforms {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
log.Info(string(platform), " list builds")
|
||||||
time.Sleep(15 * time.Second)
|
for {
|
||||||
continue
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
}
|
FilterApp: []string{appID},
|
||||||
log.Info(string(platform), " ", tag, " list localizations")
|
FilterPreReleaseVersionPlatform: []string{string(platform)},
|
||||||
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
build := builds.Data[0]
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
time.Sleep(15 * time.Second)
|
||||||
log.Info("waiting for process")
|
continue
|
||||||
time.Sleep(15 * time.Second)
|
}
|
||||||
continue
|
if *build.Attributes.ProcessingState != "VALID" {
|
||||||
} else if err != nil {
|
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
|
||||||
return err
|
time.Sleep(15 * time.Second)
|
||||||
}
|
continue
|
||||||
log.Info(string(platform), " ", tag, " list submissions")
|
}
|
||||||
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
|
log.Info(string(platform), " ", tag, " list localizations")
|
||||||
FilterBuild: []string{build.ID},
|
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
|
||||||
})
|
|
||||||
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
return err
|
||||||
log.Error(err)
|
}
|
||||||
break
|
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 {
|
||||||
|
log.Info("waiting for process")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
return err
|
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
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile"
|
_ "github.com/sagernet/gomobile"
|
||||||
@@ -20,14 +19,12 @@ var (
|
|||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
// 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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -47,9 +44,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
// memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,38 +56,18 @@ 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+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||||
// 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")
|
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
type AndroidBuildConfig struct {
|
func buildAndroid() {
|
||||||
AndroidAPI int
|
build_shared.FindSDK()
|
||||||
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 == "" {
|
||||||
@@ -107,24 +83,21 @@ func checkJavaVersion() {
|
|||||||
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")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func getAndroidBindTarget() string {
|
var bindTarget string
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
return platform
|
bindTarget = platform
|
||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
return "android/arm64"
|
bindTarget = "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", strconv.Itoa(config.AndroidAPI),
|
"-androidapi", "21",
|
||||||
"-javapkg=io.nekohasekai",
|
"-javapkg=io.nekohasekai",
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
}
|
||||||
@@ -135,59 +108,34 @@ func buildAndroidVariant(config AndroidBuildConfig, bindTarget string) {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-tags", strings.Join(config.Tags, ","))
|
tags := append(sharedTags, memcTags...)
|
||||||
|
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(config.OutputName, filepath.Join(copyPath, config.OutputName))
|
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Info("copied ", config.OutputName, " to ", copyPath)
|
log.Info("copied 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 != "" {
|
||||||
@@ -195,7 +143,7 @@ func buildApple() {
|
|||||||
} else if debugEnabled {
|
} else if debugEnabled {
|
||||||
bindTarget = "ios"
|
bindTarget = "ios"
|
||||||
} else {
|
} else {
|
||||||
bindTarget = "ios,iossimulator,tvos,tvossimulator,macos"
|
bindTarget = "ios,tvos,macos"
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
@@ -203,11 +151,8 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
}
|
||||||
//if !withTailscale {
|
|
||||||
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
//}
|
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
@@ -215,10 +160,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
//if withTailscale {
|
|
||||||
// tags = append(tags, memcTags...)
|
|
||||||
//}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -48,8 +48,8 @@ func GetRuntimeEnv(key string) (string, error) {
|
|||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
return "", readErr
|
return "", readErr
|
||||||
}
|
}
|
||||||
envStrings := strings.SplitSeq(string(data), "\n")
|
envStrings := strings.Split(string(data), "\n")
|
||||||
for envItem := range envStrings {
|
for _, envItem := range envStrings {
|
||||||
envItem = strings.TrimSuffix(envItem, "\r")
|
envItem = strings.TrimSuffix(envItem, "\r")
|
||||||
envKeyValue := strings.Split(envItem, "=")
|
envKeyValue := strings.Split(envItem, "=")
|
||||||
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
|
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"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/common/shell"
|
|
||||||
)
|
|
||||||
|
|
||||||
var iperf3Path string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := main0()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main0() error {
|
|
||||||
err := shell.Exec("sudo", "ls").Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
results, err := runTests()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
return encoder.Encode(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests() ([]TestResult, error) {
|
|
||||||
boxPaths := []string{
|
|
||||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
|
||||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
|
||||||
"./sing-box",
|
|
||||||
}
|
|
||||||
stacks := []string{
|
|
||||||
"gvisor",
|
|
||||||
"system",
|
|
||||||
}
|
|
||||||
mtus := []int{
|
|
||||||
1500,
|
|
||||||
4064,
|
|
||||||
// 16384,
|
|
||||||
// 32768,
|
|
||||||
// 49152,
|
|
||||||
65535,
|
|
||||||
}
|
|
||||||
flagList := [][]string{
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
var results []TestResult
|
|
||||||
for _, boxPath := range boxPaths {
|
|
||||||
for _, stack := range stacks {
|
|
||||||
for _, mtu := range mtus {
|
|
||||||
if strings.HasPrefix(boxPath, ".") {
|
|
||||||
for _, flags := range flagList {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
|
||||||
BoxPath string `json:"box_path"`
|
|
||||||
Stack string `json:"stack"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
Flags []string `json:"flags"`
|
|
||||||
MultiThread bool `json:"multi_thread"`
|
|
||||||
UploadSpeed string `json:"upload_speed"`
|
|
||||||
DownloadSpeed string `json:"download_speed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
|
||||||
testAddress := netip.MustParseAddr("1.1.1.1")
|
|
||||||
testConfig := option.Options{
|
|
||||||
Inbounds: []option.Inbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeTun,
|
|
||||||
Options: &option.TunInboundOptions{
|
|
||||||
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
|
||||||
AutoRoute: true,
|
|
||||||
MTU: uint32(mtu),
|
|
||||||
Stack: stackName,
|
|
||||||
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
RawDefaultRule: option.RawDefaultRule{
|
|
||||||
IPCIDR: []string{testAddress.String()},
|
|
||||||
},
|
|
||||||
RuleAction: option.RuleAction{
|
|
||||||
Action: C.RuleActionTypeRouteOptions,
|
|
||||||
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
|
||||||
OverrideAddress: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AutoDetectInterface: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := include.Context(context.Background())
|
|
||||||
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tempConfig.Name())
|
|
||||||
encoder := json.NewEncoderContext(ctx, tempConfig)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(testConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "encode test config")
|
|
||||||
}
|
|
||||||
tempConfig.Close()
|
|
||||||
var sudoArgs []string
|
|
||||||
if len(flags) > 0 {
|
|
||||||
sudoArgs = append(sudoArgs, "env")
|
|
||||||
sudoArgs = append(sudoArgs, flags...)
|
|
||||||
}
|
|
||||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
|
||||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
|
||||||
boxProcess.Stdout = &stderrWriter{}
|
|
||||||
boxProcess.Stderr = io.Discard
|
|
||||||
err = boxProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.IsDarwin {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
|
||||||
} else {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverProcess := shell.Exec(iperf3Path, "-s")
|
|
||||||
serverProcess.Stdout = io.Discard
|
|
||||||
serverProcess.Stderr = io.Discard
|
|
||||||
err = serverProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start iperf3 server")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
args := []string{"-c", testAddress.String()}
|
|
||||||
if multiThread {
|
|
||||||
args = append(args, "-P", "10")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadProcess := shell.Exec(iperf3Path, args...)
|
|
||||||
output, err := uploadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
|
||||||
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
|
||||||
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
|
||||||
|
|
||||||
result = &TestResult{
|
|
||||||
BoxPath: boxPath,
|
|
||||||
Stack: stackName,
|
|
||||||
MTU: mtu,
|
|
||||||
Flags: flags,
|
|
||||||
MultiThread: multiThread,
|
|
||||||
UploadSpeed: uploadResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
|
||||||
output, err = downloadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
|
||||||
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
|
||||||
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
|
||||||
|
|
||||||
result.DownloadSpeed = downloadResult
|
|
||||||
|
|
||||||
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
|
||||||
if len(flags) > 0 {
|
|
||||||
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
|
||||||
}
|
|
||||||
if multiThread {
|
|
||||||
printArgs = append(printArgs, "(-P 10)")
|
|
||||||
}
|
|
||||||
fmt.Println(printArgs...)
|
|
||||||
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
boxProcess.Cmd.Wait()
|
|
||||||
close(boxDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
serverProcess.Process.Wait()
|
|
||||||
close(serverDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-boxDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
boxProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("box process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-serverDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
serverProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("server process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type stderrWriter struct{}
|
|
||||||
|
|
||||||
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return os.Stderr.Write(p)
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,7 @@ func main() {
|
|||||||
common.Must(os.Chdir(androidPath))
|
common.Must(os.Chdir(androidPath))
|
||||||
localProps := common.Must1(os.ReadFile("version.properties"))
|
localProps := common.Must1(os.ReadFile("version.properties"))
|
||||||
var propsList [][]string
|
var propsList [][]string
|
||||||
for propLine := range strings.SplitSeq(string(localProps), "\n") {
|
for _, propLine := range strings.Split(string(localProps), "\n") {
|
||||||
propsList = append(propsList, strings.Split(propLine, "="))
|
propsList = append(propsList, strings.Split(propLine, "="))
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -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 := strings.Trim(projectContent[versionStart:versionEnd], "\"")
|
version := 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,10 +17,6 @@ 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 {
|
||||||
@@ -45,8 +41,10 @@ package certificate
|
|||||||
|
|
||||||
import "crypto/x509"
|
import "crypto/x509"
|
||||||
|
|
||||||
func newMozillaIncluded() *x509.CertPool {
|
var mozillaIncluded *x509.CertPool
|
||||||
pool := x509.NewCertPool()
|
|
||||||
|
func init() {
|
||||||
|
mozillaIncluded = x509.NewCertPool()
|
||||||
`)
|
`)
|
||||||
for {
|
for {
|
||||||
record, err := reader.Read()
|
record, err := reader.Read()
|
||||||
@@ -61,102 +59,13 @@ func newMozillaIncluded() *x509.CertPool {
|
|||||||
generated.WriteString("\n // ")
|
generated.WriteString("\n // ")
|
||||||
generated.WriteString(record[nameIndex])
|
generated.WriteString(record[nameIndex])
|
||||||
generated.WriteString("\n")
|
generated.WriteString("\n")
|
||||||
generated.WriteString(" pool.AppendCertsFromPEM([]byte(`")
|
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
cert := record[certIndex]
|
cert := record[certIndex]
|
||||||
// Remove single quotes
|
// Remove single quotes
|
||||||
cert = cert[1 : len(cert)-1]
|
cert = cert[1 : len(cert)-1]
|
||||||
generated.WriteString(cert)
|
generated.WriteString(cert)
|
||||||
generated.WriteString("`))\n")
|
generated.WriteString("`))\n")
|
||||||
}
|
}
|
||||||
generated.WriteString("\treturn pool\n}\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"
|
|
||||||
|
|
||||||
func newChromeIncluded() *x509.CertPool {
|
|
||||||
pool := 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(" pool.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("\treturn pool\n}\n")
|
|
||||||
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -61,17 +61,16 @@ func geoipExport(countryCode string) error {
|
|||||||
outputFile *os.File
|
outputFile *os.File
|
||||||
outputWriter io.Writer
|
outputWriter io.Writer
|
||||||
)
|
)
|
||||||
switch flagGeoipExportOutput {
|
if flagGeoipExportOutput == "stdout" {
|
||||||
case "stdout":
|
|
||||||
outputWriter = os.Stdout
|
outputWriter = os.Stdout
|
||||||
case flagGeoipExportDefaultOutput:
|
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
|
||||||
outputFile, err = os.Create("geoip-" + countryCode + ".json")
|
outputFile, err = os.Create("geoip-" + countryCode + ".json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outputFile.Close()
|
defer outputFile.Close()
|
||||||
outputWriter = outputFile
|
outputWriter = outputFile
|
||||||
default:
|
} else {
|
||||||
outputFile, err = os.Create(flagGeoipExportOutput)
|
outputFile, err = os.Create(flagGeoipExportOutput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -43,17 +43,16 @@ func geositeExport(category string) error {
|
|||||||
outputFile *os.File
|
outputFile *os.File
|
||||||
outputWriter io.Writer
|
outputWriter io.Writer
|
||||||
)
|
)
|
||||||
switch commandGeositeExportOutput {
|
if commandGeositeExportOutput == "stdout" {
|
||||||
case "stdout":
|
|
||||||
outputWriter = os.Stdout
|
outputWriter = os.Stdout
|
||||||
case commandGeositeExportDefaultOutput:
|
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
|
||||||
outputFile, err = os.Create("geosite-" + category + ".json")
|
outputFile, err = os.Create("geosite-" + category + ".json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outputFile.Close()
|
defer outputFile.Close()
|
||||||
outputWriter = outputFile
|
outputWriter = outputFile
|
||||||
default:
|
} else {
|
||||||
outputFile, err = os.Create(commandGeositeExportOutput)
|
outputFile, err = os.Create(commandGeositeExportOutput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
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"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -71,7 +69,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
|
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
@@ -80,18 +78,3 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
|
||||||
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
|
||||||
len(rule.DefaultInterfaceAddress) > 0
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion3
|
|
||||||
}
|
|
||||||
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion2
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
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"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
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"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func initializeHTTP3Client(instance *box.Box) error {
|
|||||||
}
|
}
|
||||||
http3Client = &http.Client{
|
http3Client = &http.Client{
|
||||||
Transport: &http3.Transport{
|
Transport: &http3.Transport{
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
destination := M.ParseSocksaddr(addr)
|
destination := M.ParseSocksaddr(addr)
|
||||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||||
if dErr != nil {
|
if dErr != nil {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
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/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -63,7 +59,9 @@ parseLine:
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ruleLine = strings.TrimSuffix(ruleLine, "|")
|
if strings.HasSuffix(ruleLine, "|") {
|
||||||
|
ruleLine = ruleLine[:len(ruleLine)-1]
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
isExclude bool
|
isExclude bool
|
||||||
isSuffix bool
|
isSuffix bool
|
||||||
@@ -74,7 +72,7 @@ parseLine:
|
|||||||
)
|
)
|
||||||
if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
|
if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
|
||||||
params := common.SubstringAfter(ruleLine, "$")
|
params := common.SubstringAfter(ruleLine, "$")
|
||||||
for param := range strings.SplitSeq(params, ",") {
|
for _, param := range strings.Split(params, ",") {
|
||||||
paramParts := strings.Split(param, "=")
|
paramParts := strings.Split(param, "=")
|
||||||
var ignored bool
|
var ignored bool
|
||||||
if len(paramParts) > 0 && len(paramParts) <= 2 {
|
if len(paramParts) > 0 && len(paramParts) <= 2 {
|
||||||
@@ -94,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +102,9 @@ parseLine:
|
|||||||
ruleLine = ruleLine[2:]
|
ruleLine = ruleLine[2:]
|
||||||
isExclude = true
|
isExclude = true
|
||||||
}
|
}
|
||||||
ruleLine = strings.TrimSuffix(ruleLine, "|")
|
if strings.HasSuffix(ruleLine, "|") {
|
||||||
|
ruleLine = ruleLine[:len(ruleLine)-1]
|
||||||
|
}
|
||||||
if strings.HasPrefix(ruleLine, "||") {
|
if strings.HasPrefix(ruleLine, "||") {
|
||||||
ruleLine = ruleLine[2:]
|
ruleLine = ruleLine[2:]
|
||||||
isSuffix = true
|
isSuffix = true
|
||||||
@@ -120,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -159,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -167,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -291,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -404,24 +294,26 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
before, after, ok := strings.Cut(ruleLine, " ")
|
idx := strings.Index(ruleLine, " ")
|
||||||
if !ok {
|
if idx == -1 {
|
||||||
return "", os.ErrInvalid
|
return "", os.ErrInvalid
|
||||||
}
|
}
|
||||||
address, err := netip.ParseAddr(before)
|
address, err := netip.ParseAddr(ruleLine[:idx])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if !address.IsUnspecified() {
|
if !address.IsUnspecified() {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
domain := after
|
domain := ruleLine[idx+1:]
|
||||||
if !M.IsDomainName(domain) {
|
if !M.IsDomainName(domain) {
|
||||||
return "", E.New("invalid domain name: ", domain)
|
return "", E.New("invalid domain name: ", domain)
|
||||||
}
|
}
|
||||||
@@ -450,5 +342,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
@@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawConn struct {
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
|
|
||||||
IsClient *bool
|
|
||||||
IsHandshakeComplete *atomic.Bool
|
|
||||||
Vers *uint16
|
|
||||||
CipherSuite *uint16
|
|
||||||
|
|
||||||
RawInput *bytes.Buffer
|
|
||||||
Input *bytes.Reader
|
|
||||||
Hand *bytes.Buffer
|
|
||||||
|
|
||||||
CloseNotifySent *bool
|
|
||||||
CloseNotifyErr *error
|
|
||||||
|
|
||||||
In *RawHalfConn
|
|
||||||
Out *RawHalfConn
|
|
||||||
|
|
||||||
BytesSent *int64
|
|
||||||
PacketsSent *int64
|
|
||||||
|
|
||||||
ActiveCall *atomic.Int32
|
|
||||||
Tmp *[16]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {
|
|
||||||
var (
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
loaded bool
|
|
||||||
)
|
|
||||||
for _, tlsCreator := range methodRegistry {
|
|
||||||
pointer, methods, loaded = tlsCreator(rawTLSConn)
|
|
||||||
if loaded {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !loaded {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &RawConn{
|
|
||||||
pointer: pointer,
|
|
||||||
methods: methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))
|
|
||||||
|
|
||||||
rawIsClient := rawConn.FieldByName("isClient")
|
|
||||||
if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {
|
|
||||||
return nil, E.New("invalid Conn.isClient")
|
|
||||||
}
|
|
||||||
conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
|
||||||
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.isHandshakeComplete")
|
|
||||||
}
|
|
||||||
conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawVers := rawConn.FieldByName("vers")
|
|
||||||
if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("invalid Conn.vers")
|
|
||||||
}
|
|
||||||
conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCipherSuite := rawConn.FieldByName("cipherSuite")
|
|
||||||
if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("invalid Conn.cipherSuite")
|
|
||||||
}
|
|
||||||
conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawRawInput := rawConn.FieldByName("rawInput")
|
|
||||||
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.rawInput")
|
|
||||||
}
|
|
||||||
conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawInput := rawConn.FieldByName("input")
|
|
||||||
if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.input")
|
|
||||||
}
|
|
||||||
conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawHand := rawConn.FieldByName("hand")
|
|
||||||
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.hand")
|
|
||||||
}
|
|
||||||
conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
|
|
||||||
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
|
|
||||||
return nil, E.New("invalid Conn.closeNotifySent")
|
|
||||||
}
|
|
||||||
conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr")
|
|
||||||
if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("invalid Conn.closeNotifyErr")
|
|
||||||
}
|
|
||||||
conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIn := rawConn.FieldByName("in")
|
|
||||||
if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.in")
|
|
||||||
}
|
|
||||||
halfIn, err := NewRawHalfConn(rawIn, methods)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "invalid Conn.in")
|
|
||||||
}
|
|
||||||
conn.In = halfIn
|
|
||||||
|
|
||||||
rawOut := rawConn.FieldByName("out")
|
|
||||||
if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.out")
|
|
||||||
}
|
|
||||||
halfOut, err := NewRawHalfConn(rawOut, methods)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "invalid Conn.out")
|
|
||||||
}
|
|
||||||
conn.Out = halfOut
|
|
||||||
|
|
||||||
rawBytesSent := rawConn.FieldByName("bytesSent")
|
|
||||||
if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {
|
|
||||||
return nil, E.New("invalid Conn.bytesSent")
|
|
||||||
}
|
|
||||||
conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawPacketsSent := rawConn.FieldByName("packetsSent")
|
|
||||||
if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {
|
|
||||||
return nil, E.New("invalid Conn.packetsSent")
|
|
||||||
}
|
|
||||||
conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
|
||||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("invalid Conn.activeCall")
|
|
||||||
}
|
|
||||||
conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawTmp := rawConn.FieldByName("tmp")
|
|
||||||
if !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("invalid Conn.tmp")
|
|
||||||
}
|
|
||||||
conn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr()))
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) ReadRecord() error {
|
|
||||||
return c.methods.readRecord(c.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) HandlePostHandshakeMessage() error {
|
|
||||||
return c.methods.handlePostHandshakeMessage(c.pointer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {
|
|
||||||
return c.methods.writeRecordLocked(c.pointer, typ, data)
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RawHalfConn struct {
|
|
||||||
pointer unsafe.Pointer
|
|
||||||
methods *Methods
|
|
||||||
*sync.Mutex
|
|
||||||
Err *error
|
|
||||||
Version *uint16
|
|
||||||
Cipher *any
|
|
||||||
Seq *[8]byte
|
|
||||||
ScratchBuf *[13]byte
|
|
||||||
TrafficSecret *[]byte
|
|
||||||
Mac *hash.Hash
|
|
||||||
RawKey *[]byte
|
|
||||||
RawIV *[]byte
|
|
||||||
RawMac *[]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
|
|
||||||
halfConn := &RawHalfConn{
|
|
||||||
pointer: unsafe.Pointer(rawHalfConn.UnsafeAddr()),
|
|
||||||
methods: methods,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawMutex := rawHalfConn.FieldByName("Mutex")
|
|
||||||
if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.Mutex")
|
|
||||||
}
|
|
||||||
halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawErr := rawHalfConn.FieldByName("err")
|
|
||||||
if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.err")
|
|
||||||
}
|
|
||||||
halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawVersion := rawHalfConn.FieldByName("version")
|
|
||||||
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.version")
|
|
||||||
}
|
|
||||||
halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawCipher := rawHalfConn.FieldByName("cipher")
|
|
||||||
if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.cipher")
|
|
||||||
}
|
|
||||||
halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawSeq := rawHalfConn.FieldByName("seq")
|
|
||||||
if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.seq")
|
|
||||||
}
|
|
||||||
halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawScratchBuf := rawHalfConn.FieldByName("scratchBuf")
|
|
||||||
if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.scratchBuf")
|
|
||||||
}
|
|
||||||
halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret")
|
|
||||||
if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.trafficSecret")
|
|
||||||
}
|
|
||||||
halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawMac := rawHalfConn.FieldByName("mac")
|
|
||||||
if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.mac")
|
|
||||||
}
|
|
||||||
halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawKey := rawHalfConn.FieldByName("rawKey")
|
|
||||||
if rawKey.IsValid() {
|
|
||||||
if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawKey")
|
|
||||||
}
|
|
||||||
halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawIV := rawHalfConn.FieldByName("rawIV")
|
|
||||||
if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawIV")
|
|
||||||
}
|
|
||||||
halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr()))
|
|
||||||
|
|
||||||
rawMAC := rawHalfConn.FieldByName("rawMac")
|
|
||||||
if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, E.New("badtls: invalid halfConn.rawMac")
|
|
||||||
}
|
|
||||||
halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return halfConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) {
|
|
||||||
return hc.methods.decrypt(hc.pointer, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) SetErrorLocked(err error) error {
|
|
||||||
return hc.methods.setErrorLocked(hc.pointer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) {
|
|
||||||
hc.methods.setTrafficSecret(hc.pointer, suite, level, secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hc *RawHalfConn) ExplicitNonceLen() int {
|
|
||||||
return hc.methods.explicitNonceLen(hc.pointer)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,18 @@
|
|||||||
//go:build go1.25 && badlinkname
|
//go:build go1.21 && !without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/tls"
|
"github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
@@ -12,21 +21,63 @@ var _ N.ReadWaiter = (*ReadWaitConn)(nil)
|
|||||||
|
|
||||||
type ReadWaitConn struct {
|
type ReadWaitConn struct {
|
||||||
tls.Conn
|
tls.Conn
|
||||||
rawConn *RawConn
|
halfAccess *sync.Mutex
|
||||||
readWaitOptions N.ReadWaitOptions
|
rawInput *bytes.Buffer
|
||||||
|
input *bytes.Reader
|
||||||
|
hand *bytes.Buffer
|
||||||
|
readWaitOptions N.ReadWaitOptions
|
||||||
|
tlsReadRecord func() error
|
||||||
|
tlsHandlePostHandshakeMessage func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||||
if _, isReadWaitConn := conn.(N.ReadWaiter); isReadWaitConn {
|
var (
|
||||||
return conn, nil
|
loaded bool
|
||||||
|
tlsReadRecord func() error
|
||||||
|
tlsHandlePostHandshakeMessage func() error
|
||||||
|
)
|
||||||
|
for _, tlsCreator := range tlsRegistry {
|
||||||
|
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
|
||||||
|
if loaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rawConn, err := NewRawConn(conn)
|
if !loaded {
|
||||||
if err != nil {
|
return nil, os.ErrInvalid
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
|
rawHalfConn := rawConn.FieldByName("in")
|
||||||
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half conn")
|
||||||
|
}
|
||||||
|
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
|
||||||
|
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid half mutex")
|
||||||
|
}
|
||||||
|
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
|
||||||
|
rawRawInput := rawConn.FieldByName("rawInput")
|
||||||
|
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid raw input")
|
||||||
|
}
|
||||||
|
rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
|
||||||
|
rawInput0 := rawConn.FieldByName("input")
|
||||||
|
if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid input")
|
||||||
|
}
|
||||||
|
input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
|
||||||
|
rawHand := rawConn.FieldByName("hand")
|
||||||
|
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid hand")
|
||||||
|
}
|
||||||
|
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
||||||
return &ReadWaitConn{
|
return &ReadWaitConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
rawConn: rawConn,
|
halfAccess: halfAccess,
|
||||||
|
rawInput: rawInput,
|
||||||
|
input: input,
|
||||||
|
hand: hand,
|
||||||
|
tlsReadRecord: tlsReadRecord,
|
||||||
|
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,36 +87,36 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||||
//err = c.HandshakeContext(context.Background())
|
err = c.HandshakeContext(context.Background())
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
// return
|
return
|
||||||
//}
|
}
|
||||||
c.rawConn.In.Lock()
|
c.halfAccess.Lock()
|
||||||
defer c.rawConn.In.Unlock()
|
defer c.halfAccess.Unlock()
|
||||||
for c.rawConn.Input.Len() == 0 {
|
for c.input.Len() == 0 {
|
||||||
err = c.rawConn.ReadRecord()
|
err = c.tlsReadRecord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for c.rawConn.Hand.Len() > 0 {
|
for c.hand.Len() > 0 {
|
||||||
err = c.rawConn.HandlePostHandshakeMessage()
|
err = c.tlsHandlePostHandshakeMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer = c.readWaitOptions.NewBuffer()
|
buffer = c.readWaitOptions.NewBuffer()
|
||||||
n, err := c.rawConn.Input.Read(buffer.FreeBytes())
|
n, err := c.input.Read(buffer.FreeBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buffer.Release()
|
buffer.Release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buffer.Truncate(n)
|
buffer.Truncate(n)
|
||||||
|
|
||||||
if n != 0 && c.rawConn.Input.Len() == 0 && c.rawConn.Input.Len() > 0 &&
|
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
||||||
// recordType(c.RawInput.Bytes()[0]) == recordTypeAlert {
|
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
||||||
c.rawConn.RawInput.Bytes()[0] == 21 {
|
c.rawInput.Bytes()[0] == 21 {
|
||||||
_ = c.rawConn.ReadRecord()
|
_ = c.tlsReadRecord()
|
||||||
// return n, err // will be io.EOF on closeNotify
|
// return n, err // will be io.EOF on closeNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +128,24 @@ func (c *ReadWaitConn) Upstream() any {
|
|||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ReadWaitConn) ReaderReplaceable() bool {
|
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||||
return true
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
|
tlsConn, loaded := conn.(*tls.STDConn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return stdTLSReadRecord(tlsConn)
|
||||||
|
}, func() error {
|
||||||
|
return stdTLSHandlePostHandshakeMessage(tlsConn)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
||||||
|
func stdTLSReadRecord(c *tls.STDConn) error
|
||||||
|
|
||||||
|
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.25 || !badlinkname
|
//go:build !go1.21 || without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
|||||||
32
common/badtls/read_wait_utls.go
Normal file
32
common/badtls/read_wait_utls.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//go:build go1.21 && !without_badtls && with_utls
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
|
||||||
|
"github.com/metacubex/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
|
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return utlsReadRecord(tlsConn.Conn)
|
||||||
|
}, func() error {
|
||||||
|
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
||||||
|
func utlsReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
|
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Methods struct {
|
|
||||||
readRecord func(c unsafe.Pointer) error
|
|
||||||
handlePostHandshakeMessage func(c unsafe.Pointer) error
|
|
||||||
writeRecordLocked func(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
setErrorLocked func(hc unsafe.Pointer, err error) error
|
|
||||||
decrypt func(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
setTrafficSecret func(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
explicitNonceLen func(hc unsafe.Pointer) int
|
|
||||||
}
|
|
||||||
|
|
||||||
var methodRegistry []func(conn net.Conn) (unsafe.Pointer, *Methods, bool)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
|
||||||
tlsConn, loaded := conn.(*tls.Conn)
|
|
||||||
if !loaded {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return unsafe.Pointer(tlsConn), &Methods{
|
|
||||||
readRecord: stdTLSReadRecord,
|
|
||||||
handlePostHandshakeMessage: stdTLSHandlePostHandshakeMessage,
|
|
||||||
writeRecordLocked: stdWriteRecordLocked,
|
|
||||||
|
|
||||||
setErrorLocked: stdSetErrorLocked,
|
|
||||||
decrypt: stdDecrypt,
|
|
||||||
setTrafficSecret: stdSetTrafficSecret,
|
|
||||||
explicitNonceLen: stdExplicitNonceLen,
|
|
||||||
}, true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
|
||||||
func stdTLSReadRecord(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func stdTLSHandlePostHandshakeMessage(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname stdWriteRecordLocked crypto/tls.(*Conn).writeRecordLocked
|
|
||||||
func stdWriteRecordLocked(c unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
//go:linkname stdSetErrorLocked crypto/tls.(*halfConn).setErrorLocked
|
|
||||||
func stdSetErrorLocked(hc unsafe.Pointer, err error) error
|
|
||||||
|
|
||||||
//go:linkname stdDecrypt crypto/tls.(*halfConn).decrypt
|
|
||||||
func stdDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
|
|
||||||
//go:linkname stdSetTrafficSecret crypto/tls.(*halfConn).setTrafficSecret
|
|
||||||
func stdSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
|
|
||||||
//go:linkname stdExplicitNonceLen crypto/tls.(*halfConn).explicitNonceLen
|
|
||||||
func stdExplicitNonceLen(hc unsafe.Pointer) int
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
//go:build go1.25 && badlinkname
|
|
||||||
|
|
||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/metacubex/utls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
methodRegistry = append(methodRegistry, func(conn net.Conn) (unsafe.Pointer, *Methods, bool) {
|
|
||||||
var pointer unsafe.Pointer
|
|
||||||
if uConn, loaded := N.CastReader[*tls.Conn](conn); loaded {
|
|
||||||
pointer = unsafe.Pointer(uConn)
|
|
||||||
} else if uConn, loaded := N.CastReader[*tls.UConn](conn); loaded {
|
|
||||||
pointer = unsafe.Pointer(uConn.Conn)
|
|
||||||
} else {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return pointer, &Methods{
|
|
||||||
readRecord: utlsReadRecord,
|
|
||||||
handlePostHandshakeMessage: utlsHandlePostHandshakeMessage,
|
|
||||||
writeRecordLocked: utlsWriteRecordLocked,
|
|
||||||
|
|
||||||
setErrorLocked: utlsSetErrorLocked,
|
|
||||||
decrypt: utlsDecrypt,
|
|
||||||
setTrafficSecret: utlsSetTrafficSecret,
|
|
||||||
explicitNonceLen: utlsExplicitNonceLen,
|
|
||||||
}, true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname utlsReadRecord github.com/metacubex/utls.(*Conn).readRecord
|
|
||||||
func utlsReadRecord(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname utlsHandlePostHandshakeMessage github.com/metacubex/utls.(*Conn).handlePostHandshakeMessage
|
|
||||||
func utlsHandlePostHandshakeMessage(c unsafe.Pointer) error
|
|
||||||
|
|
||||||
//go:linkname utlsWriteRecordLocked github.com/metacubex/utls.(*Conn).writeRecordLocked
|
|
||||||
func utlsWriteRecordLocked(hc unsafe.Pointer, typ uint16, data []byte) (int, error)
|
|
||||||
|
|
||||||
//go:linkname utlsSetErrorLocked github.com/metacubex/utls.(*halfConn).setErrorLocked
|
|
||||||
func utlsSetErrorLocked(hc unsafe.Pointer, err error) error
|
|
||||||
|
|
||||||
//go:linkname utlsDecrypt github.com/metacubex/utls.(*halfConn).decrypt
|
|
||||||
func utlsDecrypt(hc unsafe.Pointer, record []byte) ([]byte, uint8, error)
|
|
||||||
|
|
||||||
//go:linkname utlsSetTrafficSecret github.com/metacubex/utls.(*halfConn).setTrafficSecret
|
|
||||||
func utlsSetTrafficSecret(hc unsafe.Pointer, suite unsafe.Pointer, level int, secret []byte)
|
|
||||||
|
|
||||||
//go:linkname utlsExplicitNonceLen github.com/metacubex/utls.(*halfConn).explicitNonceLen
|
|
||||||
func utlsExplicitNonceLen(hc unsafe.Pointer) int
|
|
||||||
@@ -5,8 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
|
||||||
"golang.org/x/mod/semver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
@@ -18,19 +16,7 @@ type Version struct {
|
|||||||
PreReleaseVersion int
|
PreReleaseVersion int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v Version) LessThan(anotherVersion Version) bool {
|
func (v Version) After(anotherVersion Version) bool {
|
||||||
return !v.GreaterThanOrEqual(anotherVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) LessThanOrEqual(anotherVersion Version) bool {
|
|
||||||
return v == anotherVersion || anotherVersion.GreaterThan(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) GreaterThanOrEqual(anotherVersion Version) bool {
|
|
||||||
return v == anotherVersion || v.GreaterThan(anotherVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) GreaterThan(anotherVersion Version) bool {
|
|
||||||
if v.Major > anotherVersion.Major {
|
if v.Major > anotherVersion.Major {
|
||||||
return true
|
return true
|
||||||
} else if v.Major < anotherVersion.Major {
|
} else if v.Major < anotherVersion.Major {
|
||||||
@@ -58,29 +44,19 @@ func (v Version) GreaterThan(anotherVersion Version) bool {
|
|||||||
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
} else if v.PreReleaseIdentifier == "rc" && anotherVersion.PreReleaseIdentifier == "beta" {
|
||||||
preReleaseIdentifier := parsePreReleaseIdentifier(v.PreReleaseIdentifier)
|
|
||||||
anotherPreReleaseIdentifier := parsePreReleaseIdentifier(anotherVersion.PreReleaseIdentifier)
|
|
||||||
if preReleaseIdentifier < anotherPreReleaseIdentifier {
|
|
||||||
return true
|
return true
|
||||||
} else if preReleaseIdentifier > anotherPreReleaseIdentifier {
|
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "rc" {
|
||||||
|
return false
|
||||||
|
} else if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
||||||
|
return true
|
||||||
|
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePreReleaseIdentifier(identifier string) int {
|
|
||||||
if strings.HasPrefix(identifier, "rc") {
|
|
||||||
return 1
|
|
||||||
} else if strings.HasPrefix(identifier, "beta") {
|
|
||||||
return 2
|
|
||||||
} else if strings.HasPrefix(identifier, "alpha") {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) VersionString() string {
|
func (v Version) VersionString() string {
|
||||||
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
return F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
||||||
}
|
}
|
||||||
@@ -107,12 +83,10 @@ func (v Version) BadString() string {
|
|||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValid(versionName string) bool {
|
|
||||||
return semver.IsValid("v" + versionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(versionName string) (version Version) {
|
func Parse(versionName string) (version Version) {
|
||||||
versionName = strings.TrimPrefix(versionName, "v")
|
if strings.HasPrefix(versionName, "v") {
|
||||||
|
versionName = versionName[1:]
|
||||||
|
}
|
||||||
if strings.Contains(versionName, "-") {
|
if strings.Contains(versionName, "-") {
|
||||||
parts := strings.Split(versionName, "-")
|
parts := strings.Split(versionName, "-")
|
||||||
versionName = parts[0]
|
versionName = parts[0]
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ func TestCompareVersion(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
||||||
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
||||||
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3-beta1")))
|
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
||||||
require.True(t, Parse("1.3.0").GreaterThan(Parse("1.3.0-beta1")))
|
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
||||||
require.True(t, Parse("1.3.0-beta1").GreaterThan(Parse("1.3.0-alpha1")))
|
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
||||||
require.True(t, Parse("1.3.1").GreaterThan(Parse("1.3.0")))
|
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
||||||
require.True(t, Parse("1.4").GreaterThan(Parse("1.3")))
|
require.True(t, Parse("1.4").After(Parse("1.3")))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package byteformats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
unitNames = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
iUnitNames = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
kUnitNames = []string{"kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
kiUnitNames = []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func formatBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s < 10 {
|
|
||||||
return fmt.Sprintf("%d B", s)
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
suffix := sizes[int(e)]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatKBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s == 0 {
|
|
||||||
return fmt.Sprintf("0 %s", sizes[0])
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
if e < 1 {
|
|
||||||
e = 1
|
|
||||||
}
|
|
||||||
suffix := sizes[int(e)-1]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logn(n, b float64) float64 {
|
|
||||||
return math.Log(n) / math.Log(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatBytes(s uint64) string {
|
|
||||||
return formatBytes(s, 1000, unitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMemoryBytes(s uint64) string {
|
|
||||||
return formatBytes(s, 1024, unitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatIBytes(s uint64) string {
|
|
||||||
return formatBytes(s, 1024, iUnitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatKBytes(s uint64) string {
|
|
||||||
return formatKBytes(s, 1000, kUnitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMemoryKBytes(s uint64) string {
|
|
||||||
return formatKBytes(s, 1024, kUnitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatKIBytes(s uint64) string {
|
|
||||||
return formatKBytes(s, 1024, kiUnitNames)
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
package byteformats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Byte = 1 << (iota * 10)
|
|
||||||
KiByte
|
|
||||||
MiByte
|
|
||||||
GiByte
|
|
||||||
TiByte
|
|
||||||
PiByte
|
|
||||||
EiByte
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
KByte = Byte * 1000
|
|
||||||
MByte = KByte * 1000
|
|
||||||
GByte = MByte * 1000
|
|
||||||
TByte = GByte * 1000
|
|
||||||
PByte = TByte * 1000
|
|
||||||
EByte = PByte * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var unitValueTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"k": KByte,
|
|
||||||
"kb": KByte,
|
|
||||||
"ki": KiByte,
|
|
||||||
"kib": KiByte,
|
|
||||||
"m": MByte,
|
|
||||||
"mb": MByte,
|
|
||||||
"mi": MiByte,
|
|
||||||
"mib": MiByte,
|
|
||||||
"g": GByte,
|
|
||||||
"gb": GByte,
|
|
||||||
"gi": GiByte,
|
|
||||||
"gib": GiByte,
|
|
||||||
"t": TByte,
|
|
||||||
"tb": TByte,
|
|
||||||
"ti": TiByte,
|
|
||||||
"tib": TiByte,
|
|
||||||
"p": PByte,
|
|
||||||
"pb": PByte,
|
|
||||||
"pi": PiByte,
|
|
||||||
"pib": PiByte,
|
|
||||||
"e": EByte,
|
|
||||||
"eb": EByte,
|
|
||||||
"ei": EiByte,
|
|
||||||
"eib": EiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var memoryUnitValueTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"k": KiByte,
|
|
||||||
"kb": KiByte,
|
|
||||||
"m": MiByte,
|
|
||||||
"mb": MiByte,
|
|
||||||
"g": GiByte,
|
|
||||||
"gb": GiByte,
|
|
||||||
"t": TiByte,
|
|
||||||
"tb": TiByte,
|
|
||||||
"p": PiByte,
|
|
||||||
"pb": PiByte,
|
|
||||||
"e": EiByte,
|
|
||||||
"eb": EiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var networkUnitValueTable = map[string]uint64{
|
|
||||||
"Bps": Byte,
|
|
||||||
"Kbps": KByte / 8,
|
|
||||||
"KBps": KByte,
|
|
||||||
"Mbps": MByte / 8,
|
|
||||||
"MBps": MByte,
|
|
||||||
"Gbps": GByte / 8,
|
|
||||||
"GBps": GByte,
|
|
||||||
"Tbps": TByte / 8,
|
|
||||||
"TBps": TByte,
|
|
||||||
"Pbps": PByte / 8,
|
|
||||||
"PBps": PByte,
|
|
||||||
"Ebps": EByte / 8,
|
|
||||||
"EBps": EByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawBytes struct {
|
|
||||||
value uint64
|
|
||||||
unit string
|
|
||||||
unitValue uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b rawBytes) MarshalJSON() ([]byte, error) {
|
|
||||||
if b.unit == "" {
|
|
||||||
return json.Marshal(b.value)
|
|
||||||
}
|
|
||||||
return json.Marshal(strconv.FormatUint(b.value/b.unitValue, 10) + b.unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUnit(b *rawBytes, unitTable map[string]uint64, caseSensitive bool, bytes []byte) error {
|
|
||||||
var intValue int64
|
|
||||||
err := json.Unmarshal(bytes, &intValue)
|
|
||||||
if err == nil {
|
|
||||||
b.value = uint64(intValue)
|
|
||||||
b.unit = ""
|
|
||||||
b.unitValue = 1
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var stringValue string
|
|
||||||
err = json.Unmarshal(bytes, &stringValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(stringValue) == "" {
|
|
||||||
b.value = 0
|
|
||||||
b.unit = ""
|
|
||||||
b.unitValue = 1
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
unitIndex := 0
|
|
||||||
for i, c := range stringValue {
|
|
||||||
if c < '0' || c > '9' {
|
|
||||||
unitIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if unitIndex == 0 {
|
|
||||||
return fmt.Errorf("invalid format: %s", stringValue)
|
|
||||||
}
|
|
||||||
value, err := strconv.ParseUint(stringValue[:unitIndex], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse %s: %w", stringValue[:unitIndex], err)
|
|
||||||
}
|
|
||||||
rawUnit := stringValue[unitIndex:]
|
|
||||||
var unit string
|
|
||||||
if caseSensitive {
|
|
||||||
unit = strings.TrimSpace(rawUnit)
|
|
||||||
} else {
|
|
||||||
unit = strings.TrimSpace(strings.ToLower(rawUnit))
|
|
||||||
}
|
|
||||||
unitValue, loaded := unitTable[unit]
|
|
||||||
if !loaded {
|
|
||||||
return fmt.Errorf("unsupported unit: %s", rawUnit)
|
|
||||||
}
|
|
||||||
b.value = value * unitValue
|
|
||||||
b.unit = rawUnit
|
|
||||||
b.unitValue = unitValue
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bytes struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bytes) Value() uint64 {
|
|
||||||
if b == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return b.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bytes) UnmarshalJSON(bytes []byte) error {
|
|
||||||
return parseUnit(&b.rawBytes, unitValueTable, false, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryBytes struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemoryBytes) Value() uint64 {
|
|
||||||
if m == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return m.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemoryBytes) UnmarshalJSON(bytes []byte) error {
|
|
||||||
return parseUnit(&m.rawBytes, memoryUnitValueTable, false, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkBytes struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytes) Value() uint64 {
|
|
||||||
if n == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytes) UnmarshalJSON(bytes []byte) error {
|
|
||||||
return parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkBytesCompat struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytesCompat) Value() uint64 {
|
|
||||||
if n == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytesCompat) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
|
|
||||||
if err != nil {
|
|
||||||
newErr := parseUnit(&n.rawBytes, unitValueTable, false, bytes)
|
|
||||||
if newErr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package byteformats_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/byteformats"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNetworkBytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testMap := map[string]uint64{
|
|
||||||
"1 Bps": byteformats.Byte,
|
|
||||||
"1 Kbps": byteformats.KByte / 8,
|
|
||||||
"1 KBps": byteformats.KByte,
|
|
||||||
"1 Mbps": byteformats.MByte / 8,
|
|
||||||
"1 MBps": byteformats.MByte,
|
|
||||||
"1 Gbps": byteformats.GByte / 8,
|
|
||||||
"1 GBps": byteformats.GByte,
|
|
||||||
"1 Tbps": byteformats.TByte / 8,
|
|
||||||
"1 TBps": byteformats.TByte,
|
|
||||||
"1 Pbps": byteformats.PByte / 8,
|
|
||||||
"1 PBps": byteformats.PByte,
|
|
||||||
"1k": byteformats.KByte,
|
|
||||||
"1m": byteformats.MByte,
|
|
||||||
}
|
|
||||||
for k, v := range testMap {
|
|
||||||
var nb byteformats.NetworkBytesCompat
|
|
||||||
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &nb))
|
|
||||||
require.Equal(t, v, nb.Value())
|
|
||||||
b, err := json.Marshal(nb)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "\""+k+"\"", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMemoryBytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testMap := map[string]uint64{
|
|
||||||
"1 B": byteformats.Byte,
|
|
||||||
"1 KB": byteformats.KiByte,
|
|
||||||
"1 MB": byteformats.MiByte,
|
|
||||||
"1 GB": byteformats.GiByte,
|
|
||||||
"1 TB": byteformats.TiByte,
|
|
||||||
"1 PB": byteformats.PiByte,
|
|
||||||
}
|
|
||||||
for k, v := range testMap {
|
|
||||||
var mb byteformats.MemoryBytes
|
|
||||||
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
|
|
||||||
require.Equal(t, v, mb.Value())
|
|
||||||
b, err := json.Marshal(mb)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "\""+k+"\"", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultBytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testMap := map[string]uint64{
|
|
||||||
"1 B": byteformats.Byte,
|
|
||||||
"1 KB": byteformats.KByte,
|
|
||||||
"1 KiB": byteformats.KiByte,
|
|
||||||
"1 MB": byteformats.MByte,
|
|
||||||
"1 MiB": byteformats.MiByte,
|
|
||||||
"1 GB": byteformats.GByte,
|
|
||||||
"1 GiB": byteformats.GiByte,
|
|
||||||
"1 TB": byteformats.TByte,
|
|
||||||
"1 TiB": byteformats.TiByte,
|
|
||||||
"1 PB": byteformats.PByte,
|
|
||||||
"1 PiB": byteformats.PiByte,
|
|
||||||
"1 EB": byteformats.EByte,
|
|
||||||
"1 EiB": byteformats.EiByte,
|
|
||||||
"1k": byteformats.KByte,
|
|
||||||
"1m": byteformats.MByte,
|
|
||||||
"1g": byteformats.GByte,
|
|
||||||
"1t": byteformats.TByte,
|
|
||||||
"1p": byteformats.PByte,
|
|
||||||
"1e": byteformats.EByte,
|
|
||||||
"1K": byteformats.KByte,
|
|
||||||
"1M": byteformats.MByte,
|
|
||||||
"1G": byteformats.GByte,
|
|
||||||
"1T": byteformats.TByte,
|
|
||||||
"1P": byteformats.PByte,
|
|
||||||
"1E": byteformats.EByte,
|
|
||||||
"1Ki": byteformats.KiByte,
|
|
||||||
"1Mi": byteformats.MiByte,
|
|
||||||
"1Gi": byteformats.GiByte,
|
|
||||||
"1Ti": byteformats.TiByte,
|
|
||||||
"1Pi": byteformats.PiByte,
|
|
||||||
"1Ei": byteformats.EiByte,
|
|
||||||
"1KiB": byteformats.KiByte,
|
|
||||||
"1MiB": byteformats.MiByte,
|
|
||||||
"1GiB": byteformats.GiByte,
|
|
||||||
"1TiB": byteformats.TiByte,
|
|
||||||
"1PiB": byteformats.PiByte,
|
|
||||||
"1EiB": byteformats.EiByte,
|
|
||||||
"1kB": byteformats.KByte,
|
|
||||||
"1mB": byteformats.MByte,
|
|
||||||
"1gB": byteformats.GByte,
|
|
||||||
"1tB": byteformats.TByte,
|
|
||||||
"1pB": byteformats.PByte,
|
|
||||||
"1eB": byteformats.EByte,
|
|
||||||
}
|
|
||||||
for k, v := range testMap {
|
|
||||||
var mb byteformats.Bytes
|
|
||||||
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
|
|
||||||
require.Equal(t, v, mb.Value())
|
|
||||||
b, err := json.Marshal(mb)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "\""+k+"\"", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -21,8 +21,6 @@ import (
|
|||||||
var _ adapter.CertificateStore = (*Store)(nil)
|
var _ adapter.CertificateStore = (*Store)(nil)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
access sync.RWMutex
|
|
||||||
storeType string
|
|
||||||
systemPool *x509.CertPool
|
systemPool *x509.CertPool
|
||||||
currentPool *x509.CertPool
|
currentPool *x509.CertPool
|
||||||
certificate string
|
certificate string
|
||||||
@@ -32,15 +30,11 @@ type Store struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
|
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
|
||||||
storeType := options.Store
|
|
||||||
if storeType == "" {
|
|
||||||
storeType = C.CertificateStoreSystem
|
|
||||||
}
|
|
||||||
var systemPool *x509.CertPool
|
var systemPool *x509.CertPool
|
||||||
switch storeType {
|
switch options.Store {
|
||||||
case C.CertificateStoreSystem:
|
case C.CertificateStoreSystem, "":
|
||||||
systemPool = x509.NewCertPool()
|
systemPool = x509.NewCertPool()
|
||||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var systemValid bool
|
var systemValid bool
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
for _, cert := range platformInterface.SystemCertificates() {
|
for _, cert := range platformInterface.SystemCertificates() {
|
||||||
@@ -56,13 +50,14 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
}
|
}
|
||||||
systemPool = certPool
|
systemPool = certPool
|
||||||
}
|
}
|
||||||
case C.CertificateStoreMozilla, C.CertificateStoreChrome:
|
case C.CertificateStoreMozilla:
|
||||||
|
systemPool = mozillaIncluded
|
||||||
case C.CertificateStoreNone:
|
case C.CertificateStoreNone:
|
||||||
|
systemPool = nil
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown certificate store: ", options.Store)
|
return nil, E.New("unknown certificate store: ", options.Store)
|
||||||
}
|
}
|
||||||
store := &Store{
|
store := &Store{
|
||||||
storeType: storeType,
|
|
||||||
systemPool: systemPool,
|
systemPool: systemPool,
|
||||||
certificate: strings.Join(options.Certificate, "\n"),
|
certificate: strings.Join(options.Certificate, "\n"),
|
||||||
certificatePaths: options.CertificatePath,
|
certificatePaths: options.CertificatePath,
|
||||||
@@ -120,15 +115,15 @@ func (s *Store) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Pool() *x509.CertPool {
|
func (s *Store) Pool() *x509.CertPool {
|
||||||
s.access.RLock()
|
|
||||||
defer s.access.RUnlock()
|
|
||||||
return s.currentPool
|
return s.currentPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) update() error {
|
func (s *Store) update() error {
|
||||||
currentPool, err := s.newBasePool()
|
var currentPool *x509.CertPool
|
||||||
if err != nil {
|
if s.systemPool == nil {
|
||||||
return err
|
currentPool = x509.NewCertPool()
|
||||||
|
} else {
|
||||||
|
currentPool = s.systemPool.Clone()
|
||||||
}
|
}
|
||||||
if s.certificate != "" {
|
if s.certificate != "" {
|
||||||
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
|
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
|
||||||
@@ -163,30 +158,10 @@ func (s *Store) update() error {
|
|||||||
if firstErr != nil {
|
if firstErr != nil {
|
||||||
return firstErr
|
return firstErr
|
||||||
}
|
}
|
||||||
s.access.Lock()
|
|
||||||
defer s.access.Unlock()
|
|
||||||
s.currentPool = currentPool
|
s.currentPool = currentPool
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) newBasePool() (*x509.CertPool, error) {
|
|
||||||
switch s.storeType {
|
|
||||||
case C.CertificateStoreSystem:
|
|
||||||
if s.systemPool == nil {
|
|
||||||
return x509.NewCertPool(), nil
|
|
||||||
}
|
|
||||||
return s.systemPool.Clone(), nil
|
|
||||||
case C.CertificateStoreMozilla:
|
|
||||||
return newMozillaIncluded(), nil
|
|
||||||
case C.CertificateStoreChrome:
|
|
||||||
return newChromeIncluded(), nil
|
|
||||||
case C.CertificateStoreNone:
|
|
||||||
return x509.NewCertPool(), nil
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown certificate store: ", s.storeType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
|
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
|
||||||
files, err := os.ReadDir(dir)
|
files, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareApi struct {
|
|
||||||
client http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi {
|
|
||||||
api := &CloudflareApi{http.Client{Timeout: 30 * time.Second}}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(api)
|
|
||||||
}
|
|
||||||
return api
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) {
|
|
||||||
serial, err := GenerateRandomAndroidSerial()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate serial: %v", err)
|
|
||||||
}
|
|
||||||
data := Registration{
|
|
||||||
Key: publicKey,
|
|
||||||
InstallID: "",
|
|
||||||
FcmToken: "",
|
|
||||||
Tos: TimeAsCfString(time.Now()),
|
|
||||||
Model: "PC",
|
|
||||||
Serial: serial,
|
|
||||||
OsVersion: "",
|
|
||||||
KeyType: KeyTypeWg,
|
|
||||||
TunType: TunTypeWg,
|
|
||||||
Locale: "en-US",
|
|
||||||
}
|
|
||||||
jsonData, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal json: %v", err)
|
|
||||||
}
|
|
||||||
request, err := http.NewRequest("POST", ApiUrl+"/"+ApiVersion+"/reg", bytes.NewBuffer(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for k, v := range Headers {
|
|
||||||
request.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("failed to register: %v", response.StatusCode)
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(response.Body).Decode(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) EnrollKey(ctx context.Context, authToken string, id string, keyType, tunType, publicKey string) (*CloudflareProfile, error) {
|
|
||||||
deviceUpdate := DeviceUpdate{
|
|
||||||
Name: "PC",
|
|
||||||
Key: publicKey,
|
|
||||||
KeyType: keyType,
|
|
||||||
TunType: tunType,
|
|
||||||
}
|
|
||||||
jsonData, err := json.Marshal(deviceUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal json: %v", err)
|
|
||||||
}
|
|
||||||
request, err := http.NewRequest("PATCH", ApiUrl+"/"+ApiVersion+"/reg/"+id, bytes.NewBuffer(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for k, v := range Headers {
|
|
||||||
request.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
request.Header.Set("Authorization", "Bearer "+authToken)
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("failed to enroll key: %v", response.StatusCode)
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(response.Body).Decode(profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) {
|
|
||||||
request, err := http.NewRequest("GET", ApiUrl+"/"+ApiVersion+"/reg/"+id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for k, v := range Headers {
|
|
||||||
request.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
request.Header.Set("Authorization", "Bearer "+authToken)
|
|
||||||
response, err := api.client.Do(request.WithContext(ctx))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("failed to get profile: %v", response.StatusCode)
|
|
||||||
}
|
|
||||||
profile := new(CloudflareProfile)
|
|
||||||
return profile, json.NewDecoder(response.Body).Decode(profile)
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
const (
|
|
||||||
ApiUrl = "https://api.cloudflareclient.com"
|
|
||||||
ApiVersion = "v0a4471"
|
|
||||||
ConnectSNI = "consumer-masque.cloudflareclient.com"
|
|
||||||
// unused for now
|
|
||||||
ZeroTierSNI = "zt-masque.cloudflareclient.com"
|
|
||||||
ConnectURI = "https://cloudflareaccess.com"
|
|
||||||
DefaultModel = "PC"
|
|
||||||
KeyTypeWg = "curve25519"
|
|
||||||
TunTypeWg = "wireguard"
|
|
||||||
KeyTypeMasque = "secp256r1"
|
|
||||||
TunTypeMasque = "masque"
|
|
||||||
DefaultLocale = "en_US"
|
|
||||||
DefaultEndpointH2V4 = "162.159.198.2"
|
|
||||||
DefaultEndpointH2V6 = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
var Headers = map[string]string{
|
|
||||||
"User-Agent": "WARP for Android",
|
|
||||||
"CF-Client-Version": "a-6.35-4471",
|
|
||||||
"Content-Type": "application/json; charset=UTF-8",
|
|
||||||
"Connection": "Keep-Alive",
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registration struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
InstallID string `json:"install_id"`
|
|
||||||
FcmToken string `json:"fcm_token"`
|
|
||||||
Tos string `json:"tos"`
|
|
||||||
Model string `json:"model"`
|
|
||||||
Serial string `json:"serial_number"`
|
|
||||||
OsVersion string `json:"os_version"`
|
|
||||||
KeyType string `json:"key_type"`
|
|
||||||
TunType string `json:"tunnel_type"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudflareProfile struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Model string `json:"model"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
KeyType string `json:"key_type"`
|
|
||||||
TunType string `json:"tunnel_type"`
|
|
||||||
Account Account `json:"account"`
|
|
||||||
Config Config `json:"config"`
|
|
||||||
// WarpEnabled not set for ZeroTier
|
|
||||||
WarpEnabled bool `json:"warp_enabled,omitempty"`
|
|
||||||
// Waitlist not set for ZeroTier
|
|
||||||
Waitlist bool `json:"waitlist_enabled,omitempty"`
|
|
||||||
Created string `json:"created"`
|
|
||||||
Updated string `json:"updated"`
|
|
||||||
// Tos not set for ZeroTier
|
|
||||||
Tos string `json:"tos,omitempty"`
|
|
||||||
// Place not set for ZeroTier
|
|
||||||
Place int `json:"place,omitempty"`
|
|
||||||
Locale string `json:"locale"`
|
|
||||||
// Enabled not set for ZeroTier
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
InstallID string `json:"install_id"`
|
|
||||||
// Token only set for /reg call
|
|
||||||
Token string `json:"token,omitempty"`
|
|
||||||
FcmToken string `json:"fcm_token"`
|
|
||||||
// SerialNumber not set for ZeroTier
|
|
||||||
SerialNumber string `json:"serial_number,omitempty"`
|
|
||||||
Policy Policy `json:"policy"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
AccountType string `json:"account_type"`
|
|
||||||
// Created not set for ZeroTier
|
|
||||||
Created string `json:"created,omitempty"`
|
|
||||||
// Updated not set for ZeroTier
|
|
||||||
Updated string `json:"updated,omitempty"`
|
|
||||||
// Managed only set for ZeroTier
|
|
||||||
Managed string `json:"managed,omitempty"`
|
|
||||||
// Organization only set for ZeroTier
|
|
||||||
Organization string `json:"organization,omitempty"`
|
|
||||||
// PremiumData not set for ZeroTier
|
|
||||||
PremiumData int `json:"premium_data,omitempty"`
|
|
||||||
// Quota not set for ZeroTier
|
|
||||||
Quota int `json:"quota,omitempty"`
|
|
||||||
// WarpPlus not set for ZeroTier
|
|
||||||
WarpPlus bool `json:"warp_plus,omitempty"`
|
|
||||||
// ReferralCode not set for ZeroTier
|
|
||||||
ReferralCount int `json:"referral_count,omitempty"`
|
|
||||||
// ReferralRenewalCount not set for ZeroTier
|
|
||||||
ReferralRenewalCount int `json:"referral_renewal_countdown,omitempty"`
|
|
||||||
// Role not set for ZeroTier
|
|
||||||
Role string `json:"role,omitempty"`
|
|
||||||
// License not set for ZeroTier
|
|
||||||
License string `json:"license,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
ClientID string `json:"client_id"`
|
|
||||||
Peers []Peer `json:"peers"`
|
|
||||||
Interface struct {
|
|
||||||
Addresses struct {
|
|
||||||
V4 string `json:"v4"`
|
|
||||||
V6 string `json:"v6"`
|
|
||||||
} `json:"addresses"`
|
|
||||||
} `json:"interface"`
|
|
||||||
Services struct {
|
|
||||||
HTTPProxy string `json:"http_proxy"`
|
|
||||||
} `json:"services"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Peer struct {
|
|
||||||
PublicKey string `json:"public_key"`
|
|
||||||
Endpoint struct {
|
|
||||||
V4 string `json:"v4"`
|
|
||||||
V6 string `json:"v6"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Ports []int `json:"ports"`
|
|
||||||
} `json:"endpoint"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Policy struct {
|
|
||||||
TunnelProtocol string `json:"tunnel_protocol"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeviceUpdate struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
KeyType string `json:"key_type"`
|
|
||||||
TunType string `json:"tunnel_type"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIError struct {
|
|
||||||
Result interface{} `json:"result"`
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Errors []ErrorInfo `json:"errors"`
|
|
||||||
Messages []string `json:"messages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorInfo struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *APIError) Error() string {
|
|
||||||
errors := make([]string, len(e.Errors))
|
|
||||||
for i, err := range e.Errors {
|
|
||||||
errors[i] = err.Message
|
|
||||||
}
|
|
||||||
return strings.Join(errors, ",")
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CloudflareApiOption func(api *CloudflareApi)
|
|
||||||
|
|
||||||
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption {
|
|
||||||
return func(api *CloudflareApi) {
|
|
||||||
api.client.Timeout = 30 * time.Second
|
|
||||||
api.client.Transport = &http.Transport{
|
|
||||||
DialContext: dialContext,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package cloudflare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GenerateRandomAndroidSerial() (string, error) {
|
|
||||||
serial := make([]byte, 8)
|
|
||||||
if _, err := rand.Read(serial); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(serial), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TimeAsCfString(t time.Time) string {
|
|
||||||
return t.Format("2006-01-02T15:04:05.000-07:00")
|
|
||||||
}
|
|
||||||
54
common/conntrack/conn.go
Normal file
54
common/conntrack/conn.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
35
common/conntrack/killer.go
Normal file
35
common/conntrack/killer.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
55
common/conntrack/packet_conn.go
Normal file
55
common/conntrack/packet_conn.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
47
common/conntrack/track.go
Normal file
47
common/conntrack/track.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
5
common/conntrack/track_disable.go
Normal file
5
common/conntrack/track_disable.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build !with_conntrack
|
||||||
|
|
||||||
|
package conntrack
|
||||||
|
|
||||||
|
const Enabled = false
|
||||||
5
common/conntrack/track_enable.go
Normal file
5
common/conntrack/track_enable.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_conntrack
|
||||||
|
|
||||||
|
package conntrack
|
||||||
|
|
||||||
|
const Enabled = true
|
||||||
@@ -9,17 +9,18 @@ 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/atomic"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"github.com/database64128/tfo-go/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -28,28 +29,26 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tfo.Dialer
|
dialer4 tcpDialer
|
||||||
dialer6 tfo.Dialer
|
dialer6 tcpDialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
udpDialer6 net.Dialer
|
udpDialer6 net.Dialer
|
||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
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
|
||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback common.TypedValue[time.Time]
|
networkLastFallback atomic.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
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[adapter.PlatformInterface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialer net.Dialer
|
dialer net.Dialer
|
||||||
@@ -90,35 +89,37 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if defaultOptions.BindInterface != "" && !disableDefaultBind {
|
if !disableDefaultBind {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
if defaultOptions.BindInterface != "" {
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
|
|
||||||
if platformInterface != nil {
|
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
|
||||||
networkStrategy = defaultOptions.NetworkStrategy
|
|
||||||
networkType = defaultOptions.NetworkType
|
|
||||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
|
||||||
}
|
|
||||||
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
|
||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
|
||||||
}
|
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
bindFunc := networkManager.ProtectFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
} else {
|
|
||||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else if networkManager.AutoDetectInterface() {
|
||||||
|
if platformInterface != nil {
|
||||||
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
|
networkStrategy = defaultOptions.NetworkStrategy
|
||||||
|
networkType = defaultOptions.NetworkType
|
||||||
|
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||||
|
}
|
||||||
|
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||||
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
|
}
|
||||||
|
bindFunc := networkManager.ProtectFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else {
|
||||||
|
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
@@ -126,11 +127,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if networkManager != nil {
|
|
||||||
markFunc := networkManager.AutoRedirectOutputMarkFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, markFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, markFunc)
|
|
||||||
}
|
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
@@ -138,35 +134,14 @@ 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 {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
if options.DisableTCPKeepAlive {
|
// TODO: Add an option to customize the keep alive period
|
||||||
dialer.KeepAlive = -1
|
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||||
dialer.KeepAliveConfig.Enable = false
|
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(options.TCPKeepAlive)
|
|
||||||
if keepIdle == 0 {
|
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
|
||||||
}
|
|
||||||
keepInterval := time.Duration(options.TCPKeepAliveInterval)
|
|
||||||
if keepInterval == 0 {
|
|
||||||
keepInterval = C.TCPKeepAliveInterval
|
|
||||||
}
|
|
||||||
dialer.KeepAliveConfig = net.KeepAliveConfig{
|
|
||||||
Enable: true,
|
|
||||||
Idle: keepIdle,
|
|
||||||
Interval: keepInterval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
udpFragment = *options.UDPFragment
|
udpFragment = *options.UDPFragment
|
||||||
@@ -200,10 +175,19 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
}
|
}
|
||||||
if options.TCPMultiPath {
|
if options.TCPMultiPath {
|
||||||
dialer4.SetMultipathTCP(true)
|
if !go121Available {
|
||||||
|
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
||||||
|
}
|
||||||
|
setMultiPathTCP(&dialer4)
|
||||||
|
}
|
||||||
|
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
tcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen}
|
|
||||||
tcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen}
|
|
||||||
return &DefaultDialer{
|
return &DefaultDialer{
|
||||||
dialer4: tcpDialer4,
|
dialer4: tcpDialer4,
|
||||||
dialer6: tcpDialer6,
|
dialer6: tcpDialer6,
|
||||||
@@ -213,7 +197,6 @@ 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,
|
||||||
@@ -242,11 +225,11 @@ func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefaul
|
|||||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
} else if address.IsDomain() {
|
} else if address.IsFqdn() {
|
||||||
return nil, E.New("domain not resolved")
|
return nil, E.New("domain not resolved")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
return 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() {
|
||||||
@@ -284,11 +267,11 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
}
|
}
|
||||||
var dialer net.Dialer
|
var dialer net.Dialer
|
||||||
if N.NetworkName(network) == N.NetworkTCP {
|
if N.NetworkName(network) == N.NetworkTCP {
|
||||||
dialer = d.dialer4.Dialer
|
dialer = dialerFromTCPDialer(d.dialer4)
|
||||||
} else {
|
} else {
|
||||||
dialer = d.udpDialer4
|
dialer = d.udpDialer4
|
||||||
}
|
}
|
||||||
fastFallback := time.Since(d.networkLastFallback.Load()) < C.TCPTimeout
|
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||||
var (
|
var (
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
isPrimary bool
|
isPrimary bool
|
||||||
@@ -311,12 +294,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 d.trackConn(conn, nil)
|
return 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 d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
return 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() {
|
||||||
@@ -330,14 +313,6 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
|
|
||||||
if !destination.Is6() {
|
|
||||||
return d.dialer4.Dialer
|
|
||||||
} else {
|
|
||||||
return d.dialer6.Dialer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
if strategy == nil {
|
if strategy == nil {
|
||||||
strategy = d.networkStrategy
|
strategy = d.networkStrategy
|
||||||
@@ -368,23 +343,33 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d.trackPacketConn(packetConn, nil)
|
return trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) WireGuardControl() control.Func {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
return d.udpListener.Control
|
udpListener := d.udpListener
|
||||||
|
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
|
for _, wgControlFn := range WgControlFns {
|
||||||
|
err := wgControlFn(network, address, conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return udpListener.ListenPacket(context.Background(), network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
if d.connectionManager == nil || err != nil {
|
if !conntrack.Enabled || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return d.connectionManager.TrackConn(conn), nil
|
return conntrack.NewConn(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||||
if d.connectionManager == nil || err != nil {
|
if !conntrack.Enabled || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return d.connectionManager.TrackPacketConn(conn), nil
|
return conntrack.NewPacketConn(conn)
|
||||||
}
|
}
|
||||||
|
|||||||
19
common/dialer/default_go1.20.go
Normal file
19
common/dialer/default_go1.20.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build go1.20
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/metacubex/tfo-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tcpDialer = tfo.Dialer
|
||||||
|
|
||||||
|
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||||
|
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||||
|
return dialer.Dialer
|
||||||
|
}
|
||||||
11
common/dialer/default_go1.21.go
Normal file
11
common/dialer/default_go1.21.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build go1.21
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
const go121Available = true
|
||||||
|
|
||||||
|
func setMultiPathTCP(dialer *net.Dialer) {
|
||||||
|
dialer.SetMultipathTCP(true)
|
||||||
|
}
|
||||||
22
common/dialer/default_nongo1.20.go
Normal file
22
common/dialer/default_nongo1.20.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//go:build !go1.20
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tcpDialer = net.Dialer
|
||||||
|
|
||||||
|
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||||
|
if tfoEnabled {
|
||||||
|
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
|
||||||
|
}
|
||||||
|
return dialer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||||
|
return dialer
|
||||||
|
}
|
||||||
12
common/dialer/default_nongo1.21.go
Normal file
12
common/dialer/default_nongo1.21.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !go1.21
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const go121Available = false
|
||||||
|
|
||||||
|
func setMultiPathTCP(dialer *net.Dialer) {
|
||||||
|
}
|
||||||
@@ -136,16 +136,18 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
|
|||||||
go startRacer(fallbackCtx, false, iif)
|
go startRacer(fallbackCtx, false, iif)
|
||||||
}
|
}
|
||||||
var errors []error
|
var errors []error
|
||||||
for res := range results {
|
for {
|
||||||
if res.error == nil {
|
select {
|
||||||
return res.Conn, res.primary, nil
|
case res := <-results:
|
||||||
}
|
if res.error == nil {
|
||||||
errors = append(errors, res.error)
|
return res.Conn, res.primary, nil
|
||||||
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
}
|
||||||
return nil, false, E.Errors(errors...)
|
errors = append(errors, res.error)
|
||||||
|
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||||
|
return nil, false, E.Errors(errors...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false, E.Errors(errors...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
@@ -182,12 +184,6 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
|||||||
|
|
||||||
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
||||||
interfaces := networkManager.NetworkInterfaces()
|
interfaces := networkManager.NetworkInterfaces()
|
||||||
myInterface := networkManager.InterfaceMonitor().MyInterface()
|
|
||||||
if myInterface != "" {
|
|
||||||
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
|
||||||
return it.Name != myInterface
|
|
||||||
})
|
|
||||||
}
|
|
||||||
switch strategy {
|
switch strategy {
|
||||||
case C.NetworkStrategyDefault:
|
case C.NetworkStrategyDefault:
|
||||||
if len(interfaceType) == 0 {
|
if len(interfaceType) == 0 {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user