mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
Compare commits
10 Commits
testing
...
v1.12.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c443ec41 | ||
|
|
9f423afa6a | ||
|
|
260e3161bb | ||
|
|
98b994b561 | ||
|
|
6751ece696 | ||
|
|
5979d42a09 | ||
|
|
1e461e78e5 | ||
|
|
7c13182f4d | ||
|
|
19d1dd09b8 | ||
|
|
0909369edd |
31
.fpm_openwrt
31
.fpm_openwrt
@@ -1,31 +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>"
|
|
||||||
--no-deb-generate-changes
|
|
||||||
|
|
||||||
--config-files /etc/config/sing-box
|
|
||||||
--config-files /etc/sing-box/config.json
|
|
||||||
|
|
||||||
--depends ca-bundle
|
|
||||||
--depends kmod-inet-diag
|
|
||||||
--depends kmod-tun
|
|
||||||
--depends firewall4
|
|
||||||
--depends kmod-nft-queue
|
|
||||||
|
|
||||||
--before-remove release/config/openwrt.prerm
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/openwrt.conf=/etc/config/sing-box
|
|
||||||
release/config/openwrt.init=/etc/init.d/sing-box
|
|
||||||
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
26
.fpm_systemd
26
.fpm_systemd
@@ -1,26 +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/"
|
|
||||||
--vendor SagerNet
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
|
||||||
--no-deb-generate-changes
|
|
||||||
--config-files /etc/sing-box/config.json
|
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
|
||||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
|
||||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
|
||||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
|
||||||
1
.github/CRONET_GO_VERSION
vendored
1
.github/CRONET_GO_VERSION
vendored
@@ -1 +0,0 @@
|
|||||||
335e5bef5d88fc4474c9a70b865561f45a67de83
|
|
||||||
81
.github/build_alpine_apk.sh
vendored
81
.github/build_alpine_apk.sh
vendored
@@ -1,81 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
ARCHITECTURE="$1"
|
|
||||||
VERSION="$2"
|
|
||||||
BINARY_PATH="$3"
|
|
||||||
OUTPUT_PATH="$4"
|
|
||||||
|
|
||||||
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
|
|
||||||
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
|
||||||
|
|
||||||
# Convert version to APK format:
|
|
||||||
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
|
|
||||||
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
|
|
||||||
# 1.13.0 -> 1.13.0-r0
|
|
||||||
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
|
|
||||||
APK_VERSION="${APK_VERSION}-r0"
|
|
||||||
|
|
||||||
ROOT_DIR=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$ROOT_DIR"' EXIT
|
|
||||||
|
|
||||||
# Binary
|
|
||||||
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
|
|
||||||
|
|
||||||
# Config files
|
|
||||||
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
|
|
||||||
install -Dm755 "$PROJECT/release/config/sing-box.initd" "$ROOT_DIR/etc/init.d/sing-box"
|
|
||||||
install -Dm644 "$PROJECT/release/config/sing-box.confd" "$ROOT_DIR/etc/conf.d/sing-box"
|
|
||||||
|
|
||||||
# Service files
|
|
||||||
install -Dm644 "$PROJECT/release/config/sing-box.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box.service"
|
|
||||||
install -Dm644 "$PROJECT/release/config/sing-box@.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box@.service"
|
|
||||||
|
|
||||||
# Completions
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
|
|
||||||
|
|
||||||
# License
|
|
||||||
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
|
|
||||||
|
|
||||||
# APK metadata
|
|
||||||
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
|
|
||||||
mkdir -p "$PACKAGES_DIR"
|
|
||||||
|
|
||||||
# .conffiles
|
|
||||||
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
|
|
||||||
/etc/conf.d/sing-box
|
|
||||||
/etc/init.d/sing-box
|
|
||||||
/etc/sing-box/config.json
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# .conffiles_static (sha256 checksums)
|
|
||||||
while IFS= read -r conffile; do
|
|
||||||
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
|
|
||||||
echo "$conffile $sha256"
|
|
||||||
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
|
||||||
|
|
||||||
# .list (all files, excluding lib/apk/packages/ metadata)
|
|
||||||
(cd "$ROOT_DIR" && find . -type f -o -type l) \
|
|
||||||
| sed 's|^\./|/|' \
|
|
||||||
| grep -v '^/lib/apk/packages/' \
|
|
||||||
| sort > "$PACKAGES_DIR/.list"
|
|
||||||
|
|
||||||
# Build APK
|
|
||||||
apk mkpkg \
|
|
||||||
--info "name:sing-box" \
|
|
||||||
--info "version:${APK_VERSION}" \
|
|
||||||
--info "description:The universal proxy platform." \
|
|
||||||
--info "arch:${ARCHITECTURE}" \
|
|
||||||
--info "license:GPL-3.0-or-later with name use or association addition" \
|
|
||||||
--info "origin:sing-box" \
|
|
||||||
--info "url:https://sing-box.sagernet.org/" \
|
|
||||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
|
||||||
--files "$ROOT_DIR" \
|
|
||||||
--output "$OUTPUT_PATH"
|
|
||||||
80
.github/build_openwrt_apk.sh
vendored
80
.github/build_openwrt_apk.sh
vendored
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
ARCHITECTURE="$1"
|
|
||||||
VERSION="$2"
|
|
||||||
BINARY_PATH="$3"
|
|
||||||
OUTPUT_PATH="$4"
|
|
||||||
|
|
||||||
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
|
|
||||||
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
|
||||||
|
|
||||||
# Convert version to APK format:
|
|
||||||
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
|
|
||||||
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
|
|
||||||
# 1.13.0 -> 1.13.0-r0
|
|
||||||
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
|
|
||||||
APK_VERSION="${APK_VERSION}-r0"
|
|
||||||
|
|
||||||
ROOT_DIR=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$ROOT_DIR"' EXIT
|
|
||||||
|
|
||||||
# Binary
|
|
||||||
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
|
|
||||||
|
|
||||||
# Config files
|
|
||||||
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
|
|
||||||
install -Dm644 "$PROJECT/release/config/openwrt.conf" "$ROOT_DIR/etc/config/sing-box"
|
|
||||||
install -Dm755 "$PROJECT/release/config/openwrt.init" "$ROOT_DIR/etc/init.d/sing-box"
|
|
||||||
install -Dm644 "$PROJECT/release/config/openwrt.keep" "$ROOT_DIR/lib/upgrade/keep.d/sing-box"
|
|
||||||
|
|
||||||
# Completions
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
|
|
||||||
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
|
|
||||||
|
|
||||||
# License
|
|
||||||
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
|
|
||||||
|
|
||||||
# APK metadata
|
|
||||||
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
|
|
||||||
mkdir -p "$PACKAGES_DIR"
|
|
||||||
|
|
||||||
# .conffiles
|
|
||||||
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
|
|
||||||
/etc/config/sing-box
|
|
||||||
/etc/sing-box/config.json
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# .conffiles_static (sha256 checksums)
|
|
||||||
while IFS= read -r conffile; do
|
|
||||||
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
|
|
||||||
echo "$conffile $sha256"
|
|
||||||
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
|
||||||
|
|
||||||
# .list (all files, excluding lib/apk/packages/ metadata)
|
|
||||||
(cd "$ROOT_DIR" && find . -type f -o -type l) \
|
|
||||||
| sed 's|^\./|/|' \
|
|
||||||
| grep -v '^/lib/apk/packages/' \
|
|
||||||
| sort > "$PACKAGES_DIR/.list"
|
|
||||||
|
|
||||||
# Build APK
|
|
||||||
apk mkpkg \
|
|
||||||
--info "name:sing-box" \
|
|
||||||
--info "version:${APK_VERSION}" \
|
|
||||||
--info "description:The universal proxy platform." \
|
|
||||||
--info "arch:${ARCHITECTURE}" \
|
|
||||||
--info "license:GPL-3.0-or-later" \
|
|
||||||
--info "origin:sing-box" \
|
|
||||||
--info "url:https://sing-box.sagernet.org/" \
|
|
||||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
|
||||||
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
|
||||||
--info "provider-priority:100" \
|
|
||||||
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
|
||||||
--files "$ROOT_DIR" \
|
|
||||||
--output "$OUTPUT_PATH"
|
|
||||||
28
.github/deb2ipk.sh
vendored
28
.github/deb2ipk.sh
vendored
@@ -1,28 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
|
|
||||||
|
|
||||||
set -e -o pipefail
|
|
||||||
|
|
||||||
PROJECT=$(dirname "$0")/../..
|
|
||||||
TMP_PATH=`mktemp -d`
|
|
||||||
cp $2 $TMP_PATH
|
|
||||||
pushd $TMP_PATH
|
|
||||||
|
|
||||||
DEB_NAME=`ls *.deb`
|
|
||||||
ar x $DEB_NAME
|
|
||||||
|
|
||||||
mkdir control
|
|
||||||
pushd control
|
|
||||||
tar xf ../control.tar.gz
|
|
||||||
rm md5sums
|
|
||||||
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
|
|
||||||
cat control
|
|
||||||
tar czf ../control.tar.gz ./*
|
|
||||||
popd
|
|
||||||
|
|
||||||
DEB_NAME=${DEB_NAME%.deb}
|
|
||||||
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
|
|
||||||
popd
|
|
||||||
|
|
||||||
cp $TMP_PATH/$DEB_NAME.ipk $3
|
|
||||||
rm -r $TMP_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.8"
|
|
||||||
PATCH_COMMITS=(
|
|
||||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
|
||||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
|
||||||
)
|
|
||||||
CURL_ARGS=(
|
|
||||||
-fL
|
|
||||||
--silent
|
|
||||||
--show-error
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
||||||
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$HOME/go"
|
|
||||||
cd "$HOME/go"
|
|
||||||
wget "https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
tar -xzf "go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
#cp -a go go_bootstrap
|
|
||||||
mv go go_osx
|
|
||||||
cd go_osx
|
|
||||||
|
|
||||||
# these patch URLs only work on golang1.25.x
|
|
||||||
# that means after golang1.26 release it must be changed
|
|
||||||
# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/
|
|
||||||
# revert:
|
|
||||||
# 33d3f603c1: "cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking"
|
|
||||||
# 937368f84e: "crypto/x509: change how we retrieve chains on darwin"
|
|
||||||
|
|
||||||
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
|
||||||
curl "${CURL_ARGS[@]}" "https://github.com/SagerNet/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external
|
|
||||||
# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the
|
|
||||||
# stdlib (crypto/x509) is compiled from patched src automatically.
|
|
||||||
#cd src
|
|
||||||
#GOROOT_BOOTSTRAP="$HOME/go/go_bootstrap" ./make.bash
|
|
||||||
#cd ../..
|
|
||||||
#rm -rf go_bootstrap "go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
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.8"
|
|
||||||
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
|
|
||||||
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"
|
|
||||||
758
.github/workflows/build.yml
vendored
758
.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.8
|
go-version: ^1.23
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -56,7 +55,7 @@ jobs:
|
|||||||
- name: Calculate version
|
- name: Calculate version
|
||||||
if: github.event_name != 'workflow_dispatch'
|
if: github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
go run -v ./cmd/internal/read_tag --nightly
|
||||||
- name: Set outputs
|
- name: Set outputs
|
||||||
id: outputs
|
id: outputs
|
||||||
run: |-
|
run: |-
|
||||||
@@ -70,583 +69,162 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, variant: purego, naive: true }
|
- name: linux_386
|
||||||
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
goos: linux
|
||||||
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: "x86_64" }
|
goarch: 386
|
||||||
|
- name: linux_amd64
|
||||||
- { os: linux, arch: arm64, variant: purego, naive: true }
|
goos: linux
|
||||||
- { os: linux, arch: arm64, variant: glibc, naive: true }
|
goarch: amd64
|
||||||
- { 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" }
|
- name: linux_arm64
|
||||||
|
goos: linux
|
||||||
- { os: linux, arch: "386", go386: sse2 }
|
goarch: arm64
|
||||||
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
|
- name: linux_arm
|
||||||
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: "i386_pentium4" }
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
- { os: linux, arch: arm, goarm: "7" }
|
goarm: 6
|
||||||
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
|
- name: linux_arm_v7
|
||||||
- { 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" }
|
goos: linux
|
||||||
|
goarch: arm
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
|
goarm: 7
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
- name: linux_s390x
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
|
goos: linux
|
||||||
- { os: linux, arch: riscv64, naive: true, variant: glibc }
|
goarch: s390x
|
||||||
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
|
- name: linux_riscv64
|
||||||
- { os: linux, arch: loong64, naive: true, variant: glibc }
|
goos: linux
|
||||||
- { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" }
|
goarch: riscv64
|
||||||
|
- name: linux_mips64le
|
||||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
goos: linux
|
||||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
goarch: mips64le
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
- name: windows_amd64
|
||||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
goos: windows
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" }
|
goarch: amd64
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat }
|
require_legacy_go: true
|
||||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
- name: windows_386
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat }
|
goos: windows
|
||||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
goarch: 386
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
require_legacy_go: true
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- name: windows_arm64
|
||||||
- { os: linux, arch: riscv64 }
|
goos: windows
|
||||||
- { os: linux, arch: loong64 }
|
goarch: arm64
|
||||||
|
- name: darwin_arm64
|
||||||
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
goos: darwin
|
||||||
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
goarch: arm64
|
||||||
|
- name: darwin_amd64
|
||||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android23" }
|
goos: darwin
|
||||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi23" }
|
goarch: amd64
|
||||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android23" }
|
require_legacy_go: true
|
||||||
- { os: android, arch: "386", ndk: "i686-linux-android23" }
|
- name: android_arm64
|
||||||
|
goos: android
|
||||||
|
goarch: arm64
|
||||||
|
- name: android_arm
|
||||||
|
goos: android
|
||||||
|
goarch: arm
|
||||||
|
goarm: 7
|
||||||
|
- name: android_amd64
|
||||||
|
goos: android
|
||||||
|
goarch: amd64
|
||||||
|
- name: android_386
|
||||||
|
goos: android
|
||||||
|
goarch: 386
|
||||||
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 }}
|
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.8
|
go-version: ^1.23
|
||||||
- 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/go1.20.14
|
||||||
key: go_win7_1258
|
key: go120
|
||||||
- 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.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_go_for_windows7.sh
|
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||||
- name: Setup Go for Windows 7
|
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||||
if: matrix.legacy_win7
|
mv go $HOME/go/go1.20.14
|
||||||
run: |-
|
|
||||||
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
|
|
||||||
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
|
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.goos == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28-beta2
|
||||||
local-cache: true
|
local-cache: true
|
||||||
- name: Clone cronet-go
|
- name: Setup Goreleaser
|
||||||
if: matrix.naive
|
uses: goreleaser/goreleaser-action@v6
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
|
||||||
git init ~/cronet-go
|
|
||||||
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
|
||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
|
||||||
- name: Regenerate Debian keyring
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
|
||||||
cd ~/cronet-go
|
|
||||||
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
|
||||||
- name: Cache Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
id: cache-chromium-toolchain
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
with:
|
||||||
path: |
|
distribution: goreleaser-pro
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
version: 2.5.1
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
install-only: true
|
||||||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
- name: Extract signing key
|
||||||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
run: |-
|
||||||
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
mkdir -p $HOME/.gnupg
|
||||||
- name: Download Chromium toolchain
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
if: matrix.naive
|
${{ secrets.GPG_KEY }}
|
||||||
run: |
|
EOF
|
||||||
set -xeuo pipefail
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
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"
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
- name: Set build tags
|
- name: Build
|
||||||
run: |
|
if: matrix.goos != 'android'
|
||||||
set -xeuo pipefail
|
run: |-
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
goreleaser release --clean --split
|
||||||
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}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (purego)
|
|
||||||
if: matrix.variant == 'purego'
|
|
||||||
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:
|
env:
|
||||||
CGO_ENABLED: "0"
|
GOOS: ${{ matrix.goos }}
|
||||||
GOOS: ${{ matrix.os }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOPATH: ${{ env.HOME }}/go
|
||||||
GO386: ${{ matrix.go386 }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
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
|
|
||||||
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 }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
- name: Build Android
|
- name: Build Android
|
||||||
if: matrix.os == 'android'
|
if: matrix.goos == 'android'
|
||||||
run: |
|
run: |-
|
||||||
set -xeuo pipefail
|
|
||||||
go install -v ./cmd/internal/build
|
go install -v ./cmd/internal/build
|
||||||
export CC='${{ matrix.ndk }}-clang'
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build goreleaser release --clean --split
|
||||||
export CXX="${CC}++"
|
|
||||||
mkdir -p dist
|
|
||||||
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=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
BUILD_GOOS: ${{ matrix.goos }}
|
||||||
BUILD_GOOS: ${{ matrix.os }}
|
BUILD_GOARCH: ${{ matrix.goarch }}
|
||||||
BUILD_GOARCH: ${{ matrix.arch }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
run: |-
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
|
||||||
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
|
||||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
|
||||||
elif [[ -n "${{ matrix.legacy_name }}" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-legacy-${{ matrix.legacy_name }}"
|
|
||||||
fi
|
|
||||||
if [[ "${{ matrix.variant }}" == "glibc" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-glibc"
|
|
||||||
elif [[ "${{ matrix.variant }}" == "musl" ]]; then
|
|
||||||
DIR_NAME="${DIR_NAME}-musl"
|
|
||||||
fi
|
|
||||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Package DEB
|
|
||||||
if: matrix.debian != ''
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y debsigs
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
|
||||||
--architecture ${{ matrix.debian }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
|
||||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
|
||||||
rm -rf $HOME/.gnupg
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
|
||||||
- name: Package RPM
|
|
||||||
if: matrix.rpm != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
|
||||||
--architecture ${{ matrix.rpm }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
|
||||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
|
||||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
EOF
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
rpmsign --addsign dist/*.rpm
|
|
||||||
- name: Package Pacman
|
|
||||||
if: matrix.pacman != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libarchive-tools
|
|
||||||
cp .fpm_pacman .fpm
|
|
||||||
fpm -t pacman \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
|
||||||
--architecture ${{ matrix.pacman }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
- name: Package OpenWrt
|
|
||||||
if: matrix.openwrt != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_openwrt .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/openwrt.deb" \
|
|
||||||
--architecture all \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
for architecture in ${{ matrix.openwrt }}; do
|
|
||||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
|
||||||
done
|
|
||||||
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
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd dist
|
|
||||||
mkdir -p "${DIR_NAME}"
|
|
||||||
cp ../LICENSE "${DIR_NAME}"
|
|
||||||
if [ '${{ matrix.os }}' = 'windows' ]; then
|
|
||||||
cp sing-box "${DIR_NAME}/sing-box.exe"
|
|
||||||
zip -r "${DIR_NAME}.zip" "${DIR_NAME}"
|
|
||||||
else
|
|
||||||
cp sing-box "${DIR_NAME}"
|
|
||||||
if [ -f libcronet.so ]; then
|
|
||||||
cp libcronet.so "${DIR_NAME}"
|
|
||||||
fi
|
|
||||||
tar -czvf "${DIR_NAME}.tar.gz" "${DIR_NAME}"
|
|
||||||
fi
|
|
||||||
rm -r "${DIR_NAME}"
|
|
||||||
- name: Cleanup
|
|
||||||
run: rm -f dist/sing-box dist/libcronet.so
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}${{ matrix.variant && format('-{0}', matrix.variant) }}
|
name: binary-${{ matrix.name }}
|
||||||
path: "dist"
|
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
|
|
||||||
run: rm dist/sing-box
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binary-darwin_${{ matrix.arch }}${{ matrix.legacy_name && format('-legacy-{0}', matrix.legacy_name) }}
|
|
||||||
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"
|
|
||||||
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.8
|
go-version: ^1.23
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28-beta3
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -664,12 +242,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
|
||||||
@@ -678,65 +256,50 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ~/.gradle
|
path: ~/.gradle
|
||||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
- name: Update version
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |-
|
|
||||||
go run -v ./cmd/internal/update_android_version --ci
|
|
||||||
- name: Update nightly version
|
|
||||||
if: github.event_name != 'workflow_dispatch'
|
|
||||||
run: |-
|
|
||||||
go run -v ./cmd/internal/update_android_version --ci --nightly
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |-
|
run: |-
|
||||||
|
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
|
||||||
./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 }}
|
||||||
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
|
||||||
- name: Prepare upload
|
- name: Prepare upload
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p dist
|
mkdir -p dist/release
|
||||||
#cp clients/android/app/build/outputs/apk/play/release/*.apk dist
|
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
|
||||||
cp clients/android/app/build/outputs/apk/other/release/*.apk dist
|
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
|
||||||
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
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: binary-android-apks
|
name: binary-android-apks
|
||||||
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.8
|
go-version: ^1.23
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28-beta3
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -754,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
|
||||||
@@ -772,7 +335,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 +346,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 +384,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 +392,15 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.8
|
go-version: ^1.23
|
||||||
|
- 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 +408,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
|
||||||
@@ -899,13 +469,10 @@ jobs:
|
|||||||
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
MACOS_PROJECT_VERSION=$(go run -v ./cmd/internal/app_store_connect next_macos_project_version)
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION"
|
||||||
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
echo "MACOS_PROJECT_VERSION=$MACOS_PROJECT_VERSION" >> "$GITHUB_ENV"
|
||||||
- name: Update version
|
|
||||||
if: matrix.if && matrix.name != 'iOS'
|
|
||||||
run: |-
|
|
||||||
go run -v ./cmd/internal/update_apple_version --ci
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_apple_version --ci
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
xcodebuild archive \
|
xcodebuild archive \
|
||||||
-scheme "${{ matrix.scheme }}" \
|
-scheme "${{ matrix.scheme }}" \
|
||||||
@@ -929,7 +496,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
|
||||||
@@ -954,9 +521,9 @@ jobs:
|
|||||||
zip -r SFM.dSYMs.zip dSYMs
|
zip -r SFM.dSYMs.zip dSYMs
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist/release
|
||||||
cp clients/apple/SFM.dmg "dist/SFM-${VERSION}-universal.dmg"
|
cp clients/apple/SFM.dmg "dist/release/SFM-${VERSION}-universal.dmg"
|
||||||
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/SFM-${VERSION}-universal.dSYMs.zip"
|
cp "clients/apple/${{ matrix.archive }}/SFM.dSYMs.zip" "dist/release/SFM-${VERSION}-universal.dSYMs.zip"
|
||||||
- name: Upload image
|
- name: Upload image
|
||||||
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
if: matrix.if && matrix.name == 'macOS-standalone' && github.event_name == 'workflow_dispatch'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -965,20 +532,24 @@ 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: Setup Goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
distribution: goreleaser-pro
|
||||||
|
version: 2.5.1
|
||||||
|
install-only: true
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
id: cache-ghr
|
id: cache-ghr
|
||||||
@@ -999,21 +570,30 @@ 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: Merge builds
|
||||||
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Binary'
|
||||||
|
run: |-
|
||||||
|
goreleaser continue --merge --skip publish
|
||||||
|
mkdir -p dist/release
|
||||||
|
mv dist/*/sing-box*{tar.gz,zip,deb,rpm,_amd64.pkg.tar.zst,_arm64.pkg.tar.zst} dist/release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
- name: Upload builds
|
- name: Upload builds
|
||||||
if: ${{ env.PUBLISHED == 'false' }}
|
if: ${{ env.PUBLISHED == 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Replace builds
|
- name: Replace builds
|
||||||
if: ${{ env.PUBLISHED != 'false' }}
|
if: ${{ env.PUBLISHED != 'false' }}
|
||||||
run: |-
|
run: |-
|
||||||
export PATH="$PATH:$HOME/go/bin"
|
export PATH="$PATH:$HOME/go/bin"
|
||||||
ghr --replace -p 5 "v${VERSION}" dist
|
ghr --replace -p 5 "v${VERSION}" dist/release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
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.8
|
|
||||||
- name: Clone cronet-go
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
CRONET_GO_VERSION=$(cat .github/CRONET_GO_VERSION)
|
|
||||||
git init ~/cronet-go
|
|
||||||
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
|
||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
|
||||||
- name: Regenerate Debian keyring
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
|
||||||
cd ~/cronet-go
|
|
||||||
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
|
||||||
- name: Cache Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
id: cache-chromium-toolchain
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
|
||||||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
|
||||||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
|
||||||
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
|
||||||
- name: Download Chromium toolchain
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
|
||||||
- name: Set version
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
VERSION=$(go run ./cmd/internal/read_tag)
|
|
||||||
echo "VERSION=${VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set Chromium toolchain environment
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
cd ~/cronet-go
|
|
||||||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> $GITHUB_ENV
|
|
||||||
- name: Set build tags
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
|
||||||
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (naive)
|
|
||||||
if: matrix.naive
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
- name: Build (non-naive)
|
|
||||||
if: ${{ ! matrix.naive }}
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
|
||||||
./cmd/sing-box
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
- name: Prepare artifact
|
|
||||||
run: |
|
|
||||||
platform=${{ matrix.docker_platform }}
|
|
||||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
|
||||||
# Rename binary to include arch info for Dockerfile.binary
|
|
||||||
BINARY_NAME="sing-box-${{ matrix.arch }}"
|
|
||||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
|
||||||
BINARY_NAME="${BINARY_NAME}v${{ matrix.goarm }}"
|
|
||||||
fi
|
|
||||||
mv sing-box "${BINARY_NAME}"
|
|
||||||
echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV
|
|
||||||
- name: Upload binary
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: binary-${{ env.PLATFORM_PAIR }}
|
|
||||||
path: ${{ env.BINARY_NAME }}
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
build_docker:
|
|
||||||
name: Build Docker image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- build_binary
|
|
||||||
strategy:
|
|
||||||
fail-fast: true
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { platform: "linux/amd64" }
|
|
||||||
- { platform: "linux/arm/v6" }
|
|
||||||
- { platform: "linux/arm/v7" }
|
|
||||||
- { platform: "linux/arm64" }
|
|
||||||
- { platform: "linux/386" }
|
|
||||||
# mipsle: no base Docker image available for this platform
|
|
||||||
- { platform: "linux/ppc64le" }
|
|
||||||
- { platform: "linux/riscv64" }
|
|
||||||
- { platform: "linux/s390x" }
|
|
||||||
- { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" }
|
|
||||||
steps:
|
|
||||||
- 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 }}
|
||||||
|
|||||||
21
.github/workflows/lint.yml
vendored
21
.github/workflows/lint.yml
vendored
@@ -3,20 +3,18 @@ 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
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -24,17 +22,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
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
|
go-version: ^1.23
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
verify: false
|
|
||||||
|
|||||||
241
.github/workflows/linux.yml
vendored
241
.github/workflows/linux.yml
vendored
@@ -1,243 +1,38 @@
|
|||||||
name: Build Linux Packages
|
name: Release to Linux repository
|
||||||
|
|
||||||
on:
|
on:
|
||||||
#push:
|
|
||||||
# branches:
|
|
||||||
# - stable
|
|
||||||
# - testing
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: "Version name"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
calculate_version:
|
|
||||||
name: Calculate version
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.outputs.outputs.version }}
|
|
||||||
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.8
|
|
||||||
- name: Check input version
|
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
run: |-
|
|
||||||
echo "version=${{ inputs.version }}"
|
|
||||||
echo "version=${{ inputs.version }}" >> "$GITHUB_ENV"
|
|
||||||
- name: Calculate version
|
|
||||||
if: github.event_name != 'workflow_dispatch'
|
|
||||||
run: |-
|
|
||||||
go run -v ./cmd/internal/read_tag --ci --nightly
|
|
||||||
- name: Set outputs
|
|
||||||
id: outputs
|
|
||||||
run: |-
|
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
||||||
build:
|
build:
|
||||||
name: Build binary
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
|
||||||
- calculate_version
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
# Naive-enabled builds (musl)
|
|
||||||
- { os: linux, arch: amd64, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
|
||||||
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
|
||||||
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
|
|
||||||
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel }
|
|
||||||
- { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 }
|
|
||||||
- { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 }
|
|
||||||
# Non-naive builds (unsupported architectures)
|
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
|
||||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
|
||||||
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.8
|
go-version: ^1.23
|
||||||
- name: Clone cronet-go
|
- name: Extract signing key
|
||||||
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 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
|
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
mkdir -p $HOME/.gnupg
|
||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
cat > $HOME/.gnupg/sagernet.key <<EOF
|
||||||
- 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
|
|
||||||
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 }}
|
|
||||||
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
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "0"
|
|
||||||
GOOS: ${{ matrix.os }}
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOARM: ${{ matrix.goarm }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Set mtime
|
|
||||||
run: |-
|
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
|
||||||
- name: Detect track
|
|
||||||
run: bash .github/detect_track.sh
|
|
||||||
- name: Set version
|
|
||||||
run: |-
|
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
|
||||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
|
||||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
|
||||||
- name: Package DEB
|
|
||||||
if: matrix.debian != ''
|
|
||||||
run: |
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
sudo apt-get install -y debsigs
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t deb \
|
|
||||||
--name "${NAME}" \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.debian }}.deb" \
|
|
||||||
--architecture ${{ matrix.debian }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
|
||||||
sudo patch /usr/bin/debsigs < '/tmp/debsigs.diff'
|
|
||||||
rm -rf $HOME/.gnupg
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
${{ secrets.GPG_KEY }}
|
||||||
EOF
|
EOF
|
||||||
debsigs --sign=origin -k ${{ secrets.GPG_KEY_ID }} --gpgopts '--pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}"' dist/*.deb
|
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||||
- name: Package RPM
|
- name: Publish release
|
||||||
if: matrix.rpm != ''
|
uses: goreleaser/goreleaser-action@v6
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
sudo gem install fpm
|
|
||||||
cp .fpm_systemd .fpm
|
|
||||||
fpm -t rpm \
|
|
||||||
--name "${NAME}" \
|
|
||||||
-v "$PKG_VERSION" \
|
|
||||||
-p "dist/${NAME}_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.rpm }}.rpm" \
|
|
||||||
--architecture ${{ matrix.rpm }} \
|
|
||||||
dist/sing-box=/usr/bin/sing-box
|
|
||||||
cat > $HOME/.rpmmacros <<EOF
|
|
||||||
%_gpg_name ${{ secrets.GPG_KEY_ID }}
|
|
||||||
%_gpg_sign_cmd_extra_args --pinentry-mode loopback --passphrase ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
EOF
|
|
||||||
gpg --pinentry-mode loopback --passphrase "${{ secrets.GPG_PASSPHRASE }}" --import <<EOF
|
|
||||||
${{ secrets.GPG_KEY }}
|
|
||||||
EOF
|
|
||||||
rpmsign --addsign dist/*.rpm
|
|
||||||
- name: Cleanup
|
|
||||||
run: rm dist/sing-box
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
distribution: goreleaser-pro
|
||||||
path: "dist"
|
version: latest
|
||||||
upload:
|
args: release -f .goreleaser.fury.yaml --clean
|
||||||
name: Upload builds
|
env:
|
||||||
runs-on: ubuntu-latest
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
needs:
|
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||||
- calculate_version
|
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
||||||
- build
|
NFPM_KEY_PATH: ${{ env.HOME }}/.gnupg/sagernet.key
|
||||||
steps:
|
NFPM_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- 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
|
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
|
||||||
- name: Download builds
|
|
||||||
uses: actions/download-artifact@v5
|
|
||||||
with:
|
|
||||||
path: dist
|
|
||||||
merge-multiple: true
|
|
||||||
- name: Publish packages
|
|
||||||
if: github.event_name != 'push'
|
|
||||||
run: |-
|
|
||||||
ls dist | xargs -I {} curl -F "package=@dist/{}" https://${{ secrets.FURY_TOKEN }}@push.fury.io/sagernet/
|
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -12,12 +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/
|
|
||||||
|
|||||||
@@ -1,64 +1,38 @@
|
|||||||
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.25"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
- with_dhcp
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
|
- with_ech
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- 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:
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- paralleltest
|
|
||||||
- staticcheck
|
|
||||||
settings:
|
|
||||||
staticcheck:
|
|
||||||
checks:
|
|
||||||
- all
|
|
||||||
- -S1000
|
|
||||||
- -S1008
|
|
||||||
- -S1017
|
|
||||||
- -ST1003
|
|
||||||
- -QF1001
|
|
||||||
- -QF1003
|
|
||||||
- -QF1008
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gci
|
|
||||||
- gofumpt
|
|
||||||
settings:
|
|
||||||
gci:
|
|
||||||
sections:
|
|
||||||
- standard
|
|
||||||
- prefix(github.com/sagernet/)
|
|
||||||
- default
|
|
||||||
custom-order: true
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
|
||||||
- transport/simple-obfs
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
|
|||||||
98
.goreleaser.fury.yaml
Normal file
98
.goreleaser.fury.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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_ech
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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/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 }}"
|
||||||
205
.goreleaser.yaml
Normal file
205
.goreleaser.yaml
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
version: 2
|
||||||
|
project_name: sing-box
|
||||||
|
builds:
|
||||||
|
- &template
|
||||||
|
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_ech
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
targets:
|
||||||
|
- linux_386
|
||||||
|
- linux_amd64_v1
|
||||||
|
- linux_arm64
|
||||||
|
- linux_arm_6
|
||||||
|
- linux_arm_7
|
||||||
|
- linux_s390x
|
||||||
|
- linux_riscv64
|
||||||
|
- linux_mips64le
|
||||||
|
- windows_amd64_v1
|
||||||
|
- windows_386
|
||||||
|
- windows_arm64
|
||||||
|
- darwin_amd64_v1
|
||||||
|
- darwin_arm64
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
- id: legacy
|
||||||
|
<<: *template
|
||||||
|
tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||||
|
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||||
|
targets:
|
||||||
|
- windows_amd64_v1
|
||||||
|
- windows_386
|
||||||
|
- darwin_amd64_v1
|
||||||
|
- id: android
|
||||||
|
<<: *template
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=1
|
||||||
|
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
|
||||||
|
archives:
|
||||||
|
- &template
|
||||||
|
id: archive
|
||||||
|
builds:
|
||||||
|
- main
|
||||||
|
- android
|
||||||
|
format: tar.gz
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
wrap_in_directory: true
|
||||||
|
files:
|
||||||
|
- LICENSE
|
||||||
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
- id: archive-legacy
|
||||||
|
<<: *template
|
||||||
|
builds:
|
||||||
|
- legacy
|
||||||
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||||
|
nfpms:
|
||||||
|
- 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:
|
||||||
|
- 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
|
||||||
|
- archlinux
|
||||||
|
# - apk
|
||||||
|
# - ipk
|
||||||
|
priority: extra
|
||||||
|
contents:
|
||||||
|
- src: release/config/config.json
|
||||||
|
dst: /etc/sing-box/config.json
|
||||||
|
type: config
|
||||||
|
|
||||||
|
- 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/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:
|
||||||
|
enabled: false
|
||||||
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
|
prefix_template: '{{ .ProjectName }}-{{ .Version }}/'
|
||||||
|
checksum:
|
||||||
|
disable: true
|
||||||
|
name_template: '{{ .ProjectName }}-{{ .Version }}.checksum'
|
||||||
|
signs:
|
||||||
|
- artifacts: checksum
|
||||||
|
release:
|
||||||
|
github:
|
||||||
|
owner: SagerNet
|
||||||
|
name: sing-box
|
||||||
|
draft: true
|
||||||
|
prerelease: auto
|
||||||
|
mode: replace
|
||||||
|
ids:
|
||||||
|
- archive
|
||||||
|
- package
|
||||||
|
skip_upload: true
|
||||||
|
partial:
|
||||||
|
by: target
|
||||||
13
Dockerfile
13
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
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
|
||||||
@@ -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_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
&& 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="nekohasekai <contact-git@sekai.icu>"
|
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"]
|
|
||||||
160
Makefile
160
Makefile
@@ -1,36 +1,34 @@
|
|||||||
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_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||||
|
TAGS_GO121 = with_ech
|
||||||
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||||
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|
||||||
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 ./cmd/internal/read_tag)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
export GOTOOLCHAIN=local && \
|
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
race:
|
ci_build_go120:
|
||||||
export GOTOOLCHAIN=local && \
|
go build $(PARAMS) $(MAIN)
|
||||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
export GOTOOLCHAIN=local && \
|
go build $(PARAMS) $(MAIN)
|
||||||
go build $(PARAMS) $(MAIN) && \
|
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags "$(TAGS),generate,generate_completions" $(MAIN)
|
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -40,9 +38,6 @@ fmt:
|
|||||||
@gofmt -s -w .
|
@gofmt -s -w .
|
||||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||||
|
|
||||||
fmt_docs:
|
|
||||||
go run ./cmd/internal/format_docs
|
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
go install -v github.com/daixiang0/gci@latest
|
go install -v github.com/daixiang0/gci@latest
|
||||||
@@ -55,7 +50,7 @@ lint:
|
|||||||
GOOS=freebsd golangci-lint run ./...
|
GOOS=freebsd golangci-lint run ./...
|
||||||
|
|
||||||
lint_install:
|
lint_install:
|
||||||
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@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
|
||||||
@@ -66,6 +61,9 @@ proto_install:
|
|||||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
|
update_public_suffix:
|
||||||
|
go generate common/tlsfragment/public_suffix.go
|
||||||
|
|
||||||
update_certificates:
|
update_certificates:
|
||||||
go run ./cmd/internal/update_certificates
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
@@ -92,12 +90,12 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:clean :app:assembleOtherRelease :app:assembleOtherLegacyRelease && ./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
|
||||||
|
|
||||||
@@ -112,28 +110,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 && \
|
||||||
@@ -142,65 +130,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"
|
|
||||||
|
|
||||||
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"
|
||||||
|
|
||||||
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
|
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg 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:
|
||||||
@@ -209,12 +191,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
|
||||||
@@ -237,21 +219,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.4
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.4
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -260,8 +243,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
|
||||||
@@ -271,6 +254,3 @@ update:
|
|||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|
||||||
%:
|
|
||||||
@:
|
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
> Sponsored by [Warp](https://go.warp.dev/sing-box), built for coding with multiple AI agents
|
|
||||||
|
|
||||||
<a href="https://go.warp.dev/sing-box">
|
|
||||||
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/raw/refs/heads/main/Github/Sponsor/Warp-Github-LG-02.png">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# sing-box
|
# sing-box
|
||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
providerType string
|
|
||||||
providerTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(providerType string, providerTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
providerType: providerType,
|
|
||||||
providerTag: providerTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.providerType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.providerTag
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.CertificateProviderRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
providers []adapter.CertificateProviderService
|
|
||||||
providerByTag map[string]adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
providerByTag: make(map[string]adapter.CertificateProviderService),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
providers := m.providers
|
|
||||||
m.providers = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, provider := range providers {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, provider.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", name)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) CertificateProviders() []adapter.CertificateProviderService {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.CertificateProviderService, 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.CertificateProviderService) bool {
|
|
||||||
return it == provider
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid certificate provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:index], m.providers[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return provider.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error {
|
|
||||||
provider, err := m.registry.Create(ctx, logger, tag, providerType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsProvider, loaded := m.providerByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsProvider.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close certificate-provider/", existsProvider.Type(), "[", existsProvider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
|
|
||||||
return it == existsProvider
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid certificate 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 certificate
|
|
||||||
|
|
||||||
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, logger log.ContextLogger, tag string, options T) (adapter.CertificateProviderService, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(providerType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.CertificateProviderService, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.CertificateProviderService, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructor map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructor: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(providerType string) (any, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
optionsConstructor, loaded := m.optionsType[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (adapter.CertificateProviderService, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
constructor, loaded := m.constructor[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("certificate provider type not found: " + providerType)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.optionsType[providerType] = optionsConstructor
|
|
||||||
m.constructor[providerType] = constructor
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CertificateProvider interface {
|
|
||||||
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACMECertificateProvider interface {
|
|
||||||
CertificateProvider
|
|
||||||
GetACMENextProtos() []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderService interface {
|
|
||||||
Lifecycle
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
CertificateProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderRegistry interface {
|
|
||||||
option.CertificateProviderOptionsRegistry
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (CertificateProviderService, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderManager interface {
|
|
||||||
Lifecycle
|
|
||||||
CertificateProviders() []CertificateProviderService
|
|
||||||
Get(tag string) (CertificateProviderService, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
|
|
||||||
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"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -26,38 +23,19 @@ type DNSRouter interface {
|
|||||||
|
|
||||||
type DNSClient interface {
|
type DNSClient interface {
|
||||||
Start()
|
Start()
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) 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(response *dns.Msg) 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
LookupStrategy C.DomainStrategy
|
DisableCache bool
|
||||||
DisableCache bool
|
RewriteTTL *uint32
|
||||||
DisableOptimisticCache bool
|
ClientSubnet netip.Prefix
|
||||||
RewriteTTL *uint32
|
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
|
||||||
if options == nil {
|
|
||||||
return &DNSQueryOptions{}, nil
|
|
||||||
}
|
|
||||||
transportManager := service.FromContext[DNSTransportManager](ctx)
|
|
||||||
transport, loaded := transportManager.Transport(options.Server)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("domain resolver not found: " + options.Server)
|
|
||||||
}
|
|
||||||
return &DNSQueryOptions{
|
|
||||||
Transport: transport,
|
|
||||||
Strategy: C.DomainStrategy(options.Strategy),
|
|
||||||
DisableCache: options.DisableCache,
|
|
||||||
DisableOptimisticCache: options.DisableOptimisticCache,
|
|
||||||
RewriteTTL: options.RewriteTTL,
|
|
||||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RDRCStore interface {
|
type RDRCStore interface {
|
||||||
@@ -66,15 +44,7 @@ type RDRCStore interface {
|
|||||||
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSCacheStore interface {
|
|
||||||
LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool)
|
|
||||||
SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error
|
|
||||||
SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger)
|
|
||||||
ClearDNSCache() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSTransport interface {
|
type DNSTransport interface {
|
||||||
Lifecycle
|
|
||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
@@ -82,6 +52,11 @@ type DNSTransport interface {
|
|||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegacyDNSTransport interface {
|
||||||
|
LegacyStrategy() C.DomainStrategy
|
||||||
|
LegacyClientSubnet() netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
type DNSTransportRegistry interface {
|
type DNSTransportRegistry interface {
|
||||||
option.DNSTransportOptionsRegistry
|
option.DNSTransportOptionsRegistry
|
||||||
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.EndpointManager = (*Manager)(nil)
|
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||||
@@ -48,14 +46,10 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, endpoint := range m.endpoints {
|
for _, endpoint := range m.endpoints {
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -72,15 +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(), "]")
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", name)
|
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -129,15 +119,11 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(endpoint, stage)
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||||
|
|||||||
@@ -4,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,39 +44,29 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreDNS() bool
|
|
||||||
DNSCacheStore
|
|
||||||
|
|
||||||
SetDisableExpire(disableExpire bool)
|
|
||||||
SetOptimisticTimeout(timeout time.Duration)
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedRuleSet
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedRuleSet) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedRuleSet struct {
|
||||||
Content []byte
|
Content []byte
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
LastEtag string
|
LastEtag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -87,30 +74,21 @@ 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
|
||||||
}
|
}
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
var version uint8
|
var version uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &version)
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -120,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FakeIPStore interface {
|
type FakeIPStore interface {
|
||||||
SimpleLifecycle
|
Service
|
||||||
Contains(address netip.Addr) bool
|
Contains(address netip.Addr) bool
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||||
Lookup(address netip.Addr) (string, bool)
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HTTPTransport interface {
|
|
||||||
http.RoundTripper
|
|
||||||
CloseIdleConnections()
|
|
||||||
Clone() HTTPTransport
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPClientManager interface {
|
|
||||||
ResolveTransport(ctx context.Context, logger logger.ContextLogger, options option.HTTPClientOptions) (HTTPTransport, error)
|
|
||||||
DefaultTransport() HTTPTransport
|
|
||||||
ResetNetwork()
|
|
||||||
}
|
|
||||||
@@ -2,16 +2,14 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/process"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
@@ -59,38 +57,34 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SnifferNames []string
|
|
||||||
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
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
TLSRecordFragment bool
|
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
FallbackNetworkType []C.InterfaceType
|
FallbackNetworkType []C.InterfaceType
|
||||||
FallbackDelay time.Duration
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
DNSResponse *dns.Msg
|
SourceGeoIPCode string
|
||||||
DestinationAddressMatchFromResponse bool
|
GeoIPCode string
|
||||||
SourceGeoIPCode string
|
ProcessInfo *process.Info
|
||||||
GeoIPCode string
|
QueryType uint16
|
||||||
ProcessInfo *ConnectionOwner
|
FakeIP bool
|
||||||
SourceMACAddress net.HardwareAddr
|
|
||||||
SourceHostname string
|
|
||||||
QueryType uint16
|
|
||||||
FakeIP bool
|
|
||||||
|
|
||||||
// rule cache
|
// rule cache
|
||||||
|
|
||||||
@@ -108,10 +102,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
|
||||||
@@ -119,51 +109,6 @@ func (c *InboundContext) ResetRuleMatchCache() {
|
|||||||
c.DidMatch = false
|
c.DidMatch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr {
|
|
||||||
return DNSResponseAddresses(c.DNSResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DNSResponseAddresses(response *dns.Msg) []netip.Addr {
|
|
||||||
if response == nil || response.Rcode != dns.RcodeSuccess {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
|
||||||
for _, rawRecord := range response.Answer {
|
|
||||||
switch record := rawRecord.(type) {
|
|
||||||
case *dns.A:
|
|
||||||
addr := M.AddrFromIP(record.A)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
case *dns.AAAA:
|
|
||||||
addr := M.AddrFromIP(record.AAAA)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
case *dns.HTTPS:
|
|
||||||
for _, value := range record.SVCB.Value {
|
|
||||||
switch hint := value.(type) {
|
|
||||||
case *dns.SVCBIPv4Hint:
|
|
||||||
for _, ip := range hint.Hint {
|
|
||||||
addr := M.AddrFromIP(ip).Unmap()
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *dns.SVCBIPv6Hint:
|
|
||||||
for _, ip := range hint.Hint {
|
|
||||||
addr := M.AddrFromIP(ip)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
type inboundContextKey struct{}
|
type inboundContextKey struct{}
|
||||||
|
|
||||||
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
||||||
@@ -188,7 +133,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
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -12,7 +11,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.InboundManager = (*Manager)(nil)
|
var _ adapter.InboundManager = (*Manager)(nil)
|
||||||
@@ -39,22 +37,17 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
|||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
inbounds := m.inbounds
|
for _, inbound := range m.inbounds {
|
||||||
m.access.Unlock()
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -71,15 +64,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(), "]")
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, inbound.Close(), func(err error) error {
|
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", name)
|
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -131,15 +120,11 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
if m.started {
|
if m.started {
|
||||||
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
for _, stage := range adapter.ListStartStages {
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(inbound, stage)
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDNSResponseAddressesUnmapsHTTPSIPv4Hints(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ipv4Hint := net.ParseIP("1.1.1.1")
|
|
||||||
require.NotNil(t, ipv4Hint)
|
|
||||||
|
|
||||||
response := &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Response: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
|
||||||
Answer: []dns.RR{
|
|
||||||
&dns.HTTPS{
|
|
||||||
SVCB: dns.SVCB{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: dns.Fqdn("example.com"),
|
|
||||||
Rrtype: dns.TypeHTTPS,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 60,
|
|
||||||
},
|
|
||||||
Priority: 1,
|
|
||||||
Target: ".",
|
|
||||||
Value: []dns.SVCBKeyValue{
|
|
||||||
&dns.SVCBIPv4Hint{Hint: []net.IP{ipv4Hint}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses := DNSResponseAddresses(response)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
|
|
||||||
require.True(t, addresses[0].Is4())
|
|
||||||
}
|
|
||||||
@@ -1,19 +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 {
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
@@ -56,47 +43,22 @@ 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)
|
|
||||||
logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
|
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
logger.Trace(stage, " ", service.Name())
|
|
||||||
startTime := time.Now()
|
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage.String(), " ", service.Name())
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
}
|
}
|
||||||
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
type lifecycleServiceWrapper struct {
|
||||||
SimpleLifecycle
|
Service
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
func NewLifecycleService(service Service, name string) LifecycleService {
|
||||||
return &lifecycleServiceWrapper{
|
return &lifecycleServiceWrapper{
|
||||||
SimpleLifecycle: service,
|
Service: service,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
return LegacyStart(l.SimpleLifecycle, stage)
|
return LegacyStart(l.Service, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
return l.SimpleLifecycle.Close()
|
return l.Service.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NeighborEntry struct {
|
|
||||||
Address netip.Addr
|
|
||||||
MACAddress net.HardwareAddr
|
|
||||||
Hostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborResolver interface {
|
|
||||||
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
|
|
||||||
LookupHostname(address netip.Addr) (string, bool)
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborUpdateListener interface {
|
|
||||||
UpdateNeighborTable(entries []NeighborEntry)
|
|
||||||
}
|
|
||||||
@@ -10,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
|
||||||
@@ -21,13 +20,10 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,17 +18,6 @@ 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)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -14,7 +13,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,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 {
|
||||||
@@ -46,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,40 +55,23 @@ 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() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " ", name)
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -115,29 +96,22 @@ 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 {
|
||||||
m.logger.Trace("start ", name)
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("start ", name)
|
|
||||||
err := starter.Start(adapter.StartStateStart)
|
err := starter.Start(adapter.StartStateStart)
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", name)
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
} else if starter, isStarter := outboundToStart.(interface {
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
Start() error
|
Start() error
|
||||||
}); isStarter {
|
}); isStarter {
|
||||||
m.logger.Trace("start ", name)
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("start ", name)
|
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", name)
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
}
|
}
|
||||||
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(started) == len(outbounds) {
|
if len(started) == len(outbounds) {
|
||||||
@@ -184,15 +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(), "]")
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", name)
|
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -217,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 {
|
||||||
@@ -272,20 +246,16 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if m.started {
|
|
||||||
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(outbound, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
||||||
if m.started {
|
if m.started {
|
||||||
err = common.Close(existsOutbound)
|
err = common.Close(existsOutbound)
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PlatformInterface interface {
|
|
||||||
Initialize(networkManager NetworkManager) error
|
|
||||||
|
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
|
||||||
AutoDetectInterfaceControl(fd int) error
|
|
||||||
|
|
||||||
UsePlatformInterface() bool
|
|
||||||
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
|
||||||
|
|
||||||
UsePlatformDefaultInterfaceMonitor() bool
|
|
||||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
|
||||||
|
|
||||||
UsePlatformNetworkInterfaces() bool
|
|
||||||
NetworkInterfaces() ([]NetworkInterface, error)
|
|
||||||
|
|
||||||
UnderNetworkExtension() bool
|
|
||||||
NetworkExtensionIncludeAllNetworks() bool
|
|
||||||
|
|
||||||
ClearDNSCache()
|
|
||||||
RequestPermissionForWIFIState() error
|
|
||||||
ReadWIFIState() WIFIState
|
|
||||||
SystemCertificates() []string
|
|
||||||
|
|
||||||
UsePlatformConnectionOwnerFinder() bool
|
|
||||||
FindConnectionOwner(request *FindConnectionOwnerRequest) (*ConnectionOwner, error)
|
|
||||||
|
|
||||||
UsePlatformWIFIMonitor() bool
|
|
||||||
|
|
||||||
UsePlatformNotification() bool
|
|
||||||
SendNotification(notification *Notification) error
|
|
||||||
|
|
||||||
UsePlatformNeighborResolver() bool
|
|
||||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FindConnectionOwnerRequest struct {
|
|
||||||
IpProtocol int32
|
|
||||||
SourceAddress string
|
|
||||||
SourcePort int32
|
|
||||||
DestinationAddress string
|
|
||||||
DestinationPort int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionOwner struct {
|
|
||||||
ProcessID uint32
|
|
||||||
UserId int32
|
|
||||||
UserName string
|
|
||||||
ProcessPath string
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,15 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
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/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@@ -15,14 +19,12 @@ 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
|
SetTracker(tracker ConnectionTracker)
|
||||||
NeedFindNeighbor() bool
|
|
||||||
NeighborResolver() NeighborResolver
|
|
||||||
AppendTracker(tracker ConnectionTracker)
|
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ type ConnectionRouterEx interface {
|
|||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
Name() string
|
Name() string
|
||||||
StartContext(ctx context.Context) error
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
PostStart() error
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
ExtractIPSet() []*netipx.IPSet
|
ExtractIPSet() []*netipx.IPSet
|
||||||
@@ -60,14 +62,51 @@ type RuleSet interface {
|
|||||||
|
|
||||||
type RuleSetUpdateCallback func(it RuleSet)
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type DNSRuleSetUpdateValidator interface {
|
type RuleSetMetadata struct {
|
||||||
ValidateRuleSetMetadataUpdate(tag string, metadata RuleSetMetadata) error
|
ContainsProcessRule bool
|
||||||
|
ContainsWIFIRule bool
|
||||||
|
ContainsIPCIDRRule bool
|
||||||
|
}
|
||||||
|
type HTTPStartContext struct {
|
||||||
|
ctx context.Context
|
||||||
|
access sync.Mutex
|
||||||
|
httpClientCache map[string]*http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
|
||||||
type RuleSetMetadata struct {
|
return &HTTPStartContext{
|
||||||
ContainsProcessRule bool
|
ctx: ctx,
|
||||||
ContainsWIFIRule bool
|
httpClientCache: make(map[string]*http.Client),
|
||||||
ContainsIPCIDRRule bool
|
}
|
||||||
ContainsDNSQueryTypeRule bool
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: C.TCPTimeout,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(c.ctx),
|
||||||
|
RootCAs: RootPoolFromContext(c.ctx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.httpClientCache[detour] = httpClient
|
||||||
|
return httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPStartContext) Close() {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
for _, client := range c.httpClientCache {
|
||||||
|
client.CloseIdleConnections()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HeadlessRule interface {
|
type HeadlessRule interface {
|
||||||
@@ -13,16 +11,15 @@ type HeadlessRule interface {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
SimpleLifecycle
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
LegacyPreMatch(metadata *InboundContext) bool
|
|
||||||
WithAddressLimit() bool
|
WithAddressLimit() bool
|
||||||
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleAction interface {
|
type RuleAction interface {
|
||||||
@@ -32,7 +29,7 @@ type RuleAction interface {
|
|||||||
|
|
||||||
func IsFinalAction(action RuleAction) bool {
|
func IsFinalAction(action RuleAction) bool {
|
||||||
switch action.Type() {
|
switch action.Type() {
|
||||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve, C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,27 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Lifecycle
|
Start() error
|
||||||
Type() string
|
Close() error
|
||||||
Tag() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRegistry interface {
|
|
||||||
option.ServiceOptionsRegistry
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Services() []Service
|
|
||||||
Get(tag string) (Service, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
serviceType string
|
|
||||||
serviceTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
serviceType: serviceType,
|
|
||||||
serviceTag: serviceTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.serviceType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.serviceTag
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.ServiceManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.ServiceRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
services []adapter.Service
|
|
||||||
serviceByTag map[string]adapter.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
serviceByTag: make(map[string]adapter.Service),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
services := m.services
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, service := range services {
|
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(service, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
services := m.services
|
|
||||||
m.services = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, service := range services {
|
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", name)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Services() []adapter.Service {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.services
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
service, found := m.serviceByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return service, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
service, found := m.serviceByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.serviceByTag, tag)
|
|
||||||
index := common.Index(m.services, func(it adapter.Service) bool {
|
|
||||||
return it == service
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid service index")
|
|
||||||
}
|
|
||||||
m.services = append(m.services[:index], m.services[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return service.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
|
||||||
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
name := "service/" + service.Type() + "[" + service.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(service, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsService.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
|
||||||
return it == existsService
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid service index")
|
|
||||||
}
|
|
||||||
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.services = append(m.services, service)
|
|
||||||
m.serviceByTag[tag] = service
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
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, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(outboundType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructor map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructor: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
constructor, loaded := m.constructor[outboundType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.optionsType[outboundType] = optionsConstructor
|
|
||||||
m.constructor[outboundType] = constructor
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ManagedSSMServer interface {
|
|
||||||
Inbound
|
|
||||||
SetTracker(tracker SSMTracker)
|
|
||||||
UpdateUsers(users []string, uPSKs []string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSMTracker interface {
|
|
||||||
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
|
||||||
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type TailscaleEndpoint interface {
|
|
||||||
SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error
|
|
||||||
StartTailscalePing(ctx context.Context, peerIP string, fn func(*TailscalePingResult)) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscalePingResult struct {
|
|
||||||
LatencyMs float64
|
|
||||||
IsDirect bool
|
|
||||||
Endpoint string
|
|
||||||
DERPRegionID int32
|
|
||||||
DERPRegionCode string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleEndpointStatus struct {
|
|
||||||
BackendState string
|
|
||||||
AuthURL string
|
|
||||||
NetworkName string
|
|
||||||
MagicDNSSuffix string
|
|
||||||
Self *TailscalePeer
|
|
||||||
UserGroups []*TailscaleUserGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleUserGroup struct {
|
|
||||||
UserID int64
|
|
||||||
LoginName string
|
|
||||||
DisplayName string
|
|
||||||
ProfilePicURL string
|
|
||||||
Peers []*TailscalePeer
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscalePeer struct {
|
|
||||||
HostName string
|
|
||||||
DNSName string
|
|
||||||
OS string
|
|
||||||
TailscaleIPs []string
|
|
||||||
Online bool
|
|
||||||
ExitNode bool
|
|
||||||
ExitNodeOption bool
|
|
||||||
Active bool
|
|
||||||
RxBytes int64
|
|
||||||
TxBytes int64
|
|
||||||
UserID int64
|
|
||||||
KeyExpiry int64
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,6 @@ package adapter
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TimeService interface {
|
type TimeService interface {
|
||||||
SimpleLifecycle
|
Service
|
||||||
TimeFunc() func() time.Time
|
TimeFunc() func() time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
286
box.go
286
box.go
@@ -9,21 +9,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
boxCertificate "github.com/sagernet/sing-box/adapter/certificate"
|
|
||||||
"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"
|
||||||
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/httpclient"
|
|
||||||
"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"
|
||||||
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/deprecated"
|
"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"
|
||||||
@@ -36,25 +34,22 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
service *boxService.Manager
|
dnsTransport *dns.TransportManager
|
||||||
certificateProvider *boxCertificate.Manager
|
dnsRouter *dns.Router
|
||||||
dnsTransport *dns.TransportManager
|
connection *route.ConnectionManager
|
||||||
dnsRouter *dns.Router
|
router *route.Router
|
||||||
connection *route.ConnectionManager
|
services []adapter.LifecycleService
|
||||||
router *route.Router
|
done chan struct{}
|
||||||
httpClientService adapter.LifecycleService
|
|
||||||
internalService []adapter.LifecycleService
|
|
||||||
done chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -69,8 +64,6 @@ func Context(
|
|||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
|
||||||
certificateProviderRegistry adapter.CertificateProviderRegistry,
|
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -91,14 +84,6 @@ func Context(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
|
||||||
}
|
|
||||||
if service.FromContext[adapter.CertificateProviderRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.CertificateProviderOptionsRegistry](ctx, certificateProviderRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.CertificateProviderRegistry](ctx, certificateProviderRegistry)
|
|
||||||
}
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +99,6 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
|
||||||
certificateProviderRegistry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
|
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -126,22 +109,10 @@ 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 dnsTransportRegistry == nil {
|
|
||||||
return nil, E.New("missing DNS transport registry in context")
|
|
||||||
}
|
|
||||||
if serviceRegistry == nil {
|
|
||||||
return nil, E.New("missing service registry in context")
|
|
||||||
}
|
|
||||||
if certificateProviderRegistry == nil {
|
|
||||||
return nil, E.New("missing certificate provider registry in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
@@ -154,7 +125,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
@@ -171,8 +142,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
|
||||||
var internalServices []adapter.LifecycleService
|
var services []adapter.LifecycleService
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
len(certificateOptions.Certificate) > 0 ||
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
@@ -183,38 +153,28 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
internalServices = append(internalServices, certificateStore)
|
services = append(services, certificateStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
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)
|
||||||
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)
|
|
||||||
certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry)
|
|
||||||
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.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
|
||||||
dnsRouter, err := dns.NewRouter(ctx, logFactory, dnsOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize DNS router")
|
|
||||||
}
|
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
// Must register after ConnectionManager: the Apple HTTP engine's proxy bridge reads it from the context when Manager.Start resolves the default client.
|
|
||||||
httpClientManager := httpclient.NewManager(ctx, logFactory.NewLogger("httpclient"), options.HTTPClients, routeOptions.DefaultHTTPClient)
|
|
||||||
service.MustRegister[adapter.HTTPClientManager](ctx, httpClientManager)
|
|
||||||
httpClientService := adapter.LifecycleService(httpClientManager)
|
|
||||||
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
service.MustRegister[adapter.Router](ctx, router)
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
||||||
@@ -242,7 +202,7 @@ func New(options Options) (*Box, error) {
|
|||||||
transportOptions.Options,
|
transportOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize DNS server[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = dnsRouter.Initialize(dnsOptions.Rules)
|
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||||
@@ -256,15 +216,8 @@ func New(options Options) (*Box, error) {
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
endpointCtx := ctx
|
|
||||||
if tag != "" {
|
|
||||||
// TODO: remove this
|
|
||||||
endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{
|
|
||||||
Outbound: tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
err = endpointManager.Create(
|
err = endpointManager.Create(
|
||||||
endpointCtx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@@ -272,7 +225,7 @@ func New(options Options) (*Box, error) {
|
|||||||
endpointOptions.Options,
|
endpointOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize endpoint[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
@@ -294,24 +247,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, serviceOptions := range options.Services {
|
|
||||||
var tag string
|
|
||||||
if serviceOptions.Tag != "" {
|
|
||||||
tag = serviceOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = serviceManager.Create(
|
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
serviceOptions.Type,
|
|
||||||
serviceOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
@@ -338,48 +273,22 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, certificateProviderOptions := range options.CertificateProviders {
|
outboundManager.Initialize(common.Must1(
|
||||||
var tag string
|
direct.NewOutbound(
|
||||||
if certificateProviderOptions.Tag != "" {
|
|
||||||
tag = certificateProviderOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = certificateProviderManager.Create(
|
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
certificateProviderOptions.Type,
|
|
||||||
certificateProviderOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize certificate provider[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
|
||||||
return 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{},
|
)))
|
||||||
)
|
|
||||||
})
|
|
||||||
httpClientManager.Initialize(func() (*httpclient.Transport, error) {
|
|
||||||
deprecated.Report(ctx, deprecated.OptionImplicitDefaultHTTPClient)
|
|
||||||
var httpClientOptions option.HTTPClientOptions
|
|
||||||
httpClientOptions.DefaultOutbound = true
|
|
||||||
return httpclient.NewTransport(ctx, logFactory.NewLogger("httpclient"), "", httpClientOptions)
|
|
||||||
})
|
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -387,9 +296,9 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, logFactory.NewLogger("cache-file"), common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
internalServices = append(internalServices, cacheFile)
|
services = append(services, cacheFile)
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
@@ -398,9 +307,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.AppendTracker(clashServer)
|
router.SetTracker(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
internalServices = append(internalServices, clashServer)
|
services = append(services, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@@ -408,8 +317,8 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create v2ray-server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
if v2rayServer.StatsService() != nil {
|
||||||
router.AppendTracker(v2rayServer.StatsService())
|
router.SetTracker(v2rayServer.StatsService())
|
||||||
internalServices = append(internalServices, v2rayServer)
|
services = append(services, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,25 +336,22 @@ func New(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
service: serviceManager,
|
dnsRouter: dnsRouter,
|
||||||
certificateProvider: certificateProviderManager,
|
connection: connectionManager,
|
||||||
dnsRouter: dnsRouter,
|
router: router,
|
||||||
connection: connectionManager,
|
createdAt: createdAt,
|
||||||
router: router,
|
logFactory: logFactory,
|
||||||
httpClientService: httpClientService,
|
logger: logFactory.Logger(),
|
||||||
createdAt: createdAt,
|
services: services,
|
||||||
logFactory: logFactory,
|
done: make(chan struct{}),
|
||||||
logger: logFactory.Logger(),
|
|
||||||
internalService: internalServices,
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,23 +401,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.services) // 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.service, s.certificateProvider)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStateStart, []adapter.LifecycleService{s.httpClientService})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.router, s.dnsRouter)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -523,35 +421,31 @@ 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.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint)
|
err = s.inbound.Start(adapter.StartStateStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider)
|
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
||||||
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.endpoint, s.certificateProvider, s.inbound, s.service)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
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.endpoint, s.certificateProvider, s.inbound, s.service)
|
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -565,51 +459,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
|
for _, lifecycleService := range s.services {
|
||||||
}{
|
|
||||||
{"service", s.service},
|
|
||||||
{"inbound", s.inbound},
|
|
||||||
{"certificate-provider", s.certificateProvider},
|
|
||||||
{"endpoint", s.endpoint},
|
|
||||||
{"outbound", s.outbound},
|
|
||||||
{"router", s.router},
|
|
||||||
{"connection", s.connection},
|
|
||||||
{"dns-router", s.dnsRouter},
|
|
||||||
{"dns-transport", s.dnsTransport},
|
|
||||||
{"network", s.network},
|
|
||||||
} {
|
|
||||||
s.logger.Trace("close ", closeItem.name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = E.Append(err, closeItem.service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", closeItem.name)
|
|
||||||
})
|
|
||||||
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
if s.httpClientService != nil {
|
|
||||||
s.logger.Trace("close ", s.httpClientService.Name())
|
|
||||||
startTime := time.Now()
|
|
||||||
err = E.Append(err, s.httpClientService.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", s.httpClientService.Name())
|
|
||||||
})
|
|
||||||
s.logger.Trace("close ", s.httpClientService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
for _, lifecycleService := range s.internalService {
|
|
||||||
s.logger.Trace("close ", lifecycleService.Name())
|
|
||||||
startTime := time.Now()
|
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
}
|
||||||
s.logger.Trace("close logger")
|
|
||||||
startTime := time.Now()
|
|
||||||
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close logger")
|
return E.Cause(err, "close logger")
|
||||||
})
|
})
|
||||||
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,11 +488,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: fea0f3a7ba...599d8cecac
Submodule clients/apple updated: ffbf405b52...e43e3a3f64
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/asc-go/asc"
|
"github.com/sagernet/asc-go/asc"
|
||||||
@@ -100,33 +99,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 +114,91 @@ 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 {
|
||||||
|
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,7 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
// memcTags []string
|
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,38 +55,17 @@ 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_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
||||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
// 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,87 +81,58 @@ 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",
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-tags", strings.Join(config.Tags, ","))
|
args = append(args, "-tags")
|
||||||
|
if !debugEnabled {
|
||||||
|
args = append(args, strings.Join(sharedTags, ","))
|
||||||
|
} else {
|
||||||
|
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
|
||||||
|
}
|
||||||
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 +140,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,30 +148,20 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
|
||||||
"-iosversion=15.0",
|
|
||||||
"-macosversion=13.0",
|
|
||||||
"-tvosversion=17.0",
|
|
||||||
}
|
}
|
||||||
//if !withTailscale {
|
|
||||||
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
//}
|
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
} else {
|
} else {
|
||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
//if withTailscale {
|
args = append(args, "-tags")
|
||||||
// tags = append(tags, memcTags...)
|
if !debugEnabled {
|
||||||
//}
|
args = append(args, strings.Join(tags, ","))
|
||||||
if debugEnabled {
|
} else {
|
||||||
tags = append(tags, debugTags...)
|
args = append(args, strings.Join(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...)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "28.0.13004108"
|
const fixedVersion = "28.0.12916984"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -5,49 +5,40 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
"github.com/sagernet/sing-box/common/badversion"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var nightly bool
|
||||||
flagRunInCI bool
|
|
||||||
flagRunNightly bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
flag.BoolVar(&nightly, "nightly", false, "Print nightly tag")
|
||||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
var (
|
if nightly {
|
||||||
versionStr string
|
version, err := build_shared.ReadTagVersionRev()
|
||||||
err error
|
|
||||||
)
|
|
||||||
if flagRunNightly {
|
|
||||||
var version badversion.Version
|
|
||||||
version, err = build_shared.ReadTagVersion()
|
|
||||||
if err == nil {
|
|
||||||
versionStr = version.String()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
versionStr, err = build_shared.ReadTag()
|
|
||||||
}
|
|
||||||
if flagRunInCI {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
var versionStr string
|
||||||
|
if version.PreReleaseIdentifier != "" {
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
|
} else {
|
||||||
|
version.Patch++
|
||||||
|
versionStr = version.VersionString() + "-nightly"
|
||||||
|
}
|
||||||
err = setGitHubEnv("version", versionStr)
|
err = setGitHubEnv("version", versionStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
tag, err := build_shared.ReadTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
os.Stdout.WriteString("unknown\n")
|
os.Stdout.WriteString("unknown\n")
|
||||||
} else {
|
} else {
|
||||||
os.Stdout.WriteString(versionStr + "\n")
|
os.Stdout.WriteString(tag + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -13,14 +13,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var flagRunInCI bool
|
||||||
flagRunInCI bool
|
|
||||||
flagRunNightly bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -50,23 +46,21 @@ func main() {
|
|||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
case "VERSION_NAME":
|
case "VERSION_NAME":
|
||||||
if propPair[1] != newVersion {
|
if propPair[1] != newVersion {
|
||||||
log.Info("updated version from ", propPair[1], " to ", newVersion)
|
|
||||||
versionUpdated = true
|
versionUpdated = true
|
||||||
propPair[1] = newVersion
|
propPair[1] = newVersion
|
||||||
|
log.Info("updated version to ", newVersion)
|
||||||
}
|
}
|
||||||
case "GO_VERSION":
|
case "GO_VERSION":
|
||||||
if propPair[1] != runtime.Version() {
|
if propPair[1] != runtime.Version() {
|
||||||
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
|
|
||||||
goVersionUpdated = true
|
goVersionUpdated = true
|
||||||
propPair[1] = runtime.Version()
|
propPair[1] = runtime.Version()
|
||||||
|
log.Info("updated Go version to ", runtime.Version())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !(versionUpdated || goVersionUpdated) {
|
if !(versionUpdated || goVersionUpdated) {
|
||||||
log.Info("version not changed")
|
log.Info("version not changed")
|
||||||
return
|
return
|
||||||
} else if flagRunInCI && !flagRunNightly {
|
|
||||||
log.Fatal("version changed, commit changes first.")
|
|
||||||
}
|
}
|
||||||
for _, propPair := range propsList {
|
for _, propPair := range propsList {
|
||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -18,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 {
|
||||||
@@ -36,9 +31,21 @@ func updateMozillaIncludedRootCAs() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
geoIndex := slices.Index(header, "Geographic Focus")
|
geoIndex := slices.Index(header, "Geographic Focus")
|
||||||
|
nameIndex := slices.Index(header, "Common Name or Certificate Name")
|
||||||
certIndex := slices.Index(header, "PEM Info")
|
certIndex := slices.Index(header, "PEM Info")
|
||||||
|
|
||||||
pemBundle := strings.Builder{}
|
generated := strings.Builder{}
|
||||||
|
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||||
|
|
||||||
|
package certificate
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
var mozillaIncluded *x509.CertPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mozillaIncluded = x509.NewCertPool()
|
||||||
|
`)
|
||||||
for {
|
for {
|
||||||
record, err := reader.Read()
|
record, err := reader.Read()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -49,110 +56,13 @@ func updateMozillaIncludedRootCAs() error {
|
|||||||
if record[geoIndex] == "China" {
|
if record[geoIndex] == "China" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cert := record[certIndex]
|
generated.WriteString("\n // ")
|
||||||
cert = cert[1 : len(cert)-1]
|
generated.WriteString(record[nameIndex])
|
||||||
pemBundle.WriteString(cert)
|
generated.WriteString("\n")
|
||||||
pemBundle.WriteString("\n")
|
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
|
generated.WriteString(record[certIndex])
|
||||||
|
generated.WriteString("`))\n")
|
||||||
}
|
}
|
||||||
return writeGeneratedCertificateBundle("mozilla", "mozillaIncluded", pemBundle.String())
|
generated.WriteString("}\n")
|
||||||
}
|
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
|
|
||||||
}
|
|
||||||
statusIndex := slices.Index(header, "Google Chrome Status")
|
|
||||||
certIndex := slices.Index(header, "X.509 Certificate (PEM)")
|
|
||||||
fingerprintIndex := slices.Index(header, "SHA-256 Fingerprint")
|
|
||||||
|
|
||||||
pemBundle := strings.Builder{}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
cert := record[certIndex]
|
|
||||||
if len(cert) > 0 && cert[0] == '\'' {
|
|
||||||
cert = cert[1 : len(cert)-1]
|
|
||||||
}
|
|
||||||
pemBundle.WriteString(cert)
|
|
||||||
pemBundle.WriteString("\n")
|
|
||||||
}
|
|
||||||
return writeGeneratedCertificateBundle("chrome", "chromeIncluded", pemBundle.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeGeneratedCertificateBundle(name string, variableName string, pemBundle string) error {
|
|
||||||
goSource := `// Code generated by 'make update_certificates'. DO NOT EDIT.
|
|
||||||
|
|
||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed ` + name + `.pem
|
|
||||||
var ` + variableName + `PEM string
|
|
||||||
|
|
||||||
var ` + variableName + ` *x509.CertPool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
` + variableName + ` = x509.NewCertPool()
|
|
||||||
` + variableName + `.AppendCertsFromPEM([]byte(` + variableName + `PEM))
|
|
||||||
}
|
|
||||||
`
|
|
||||||
err := os.WriteFile(filepath.Join("common/certificate", name+".pem"), []byte(pemBundle), 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(filepath.Join("common/certificate", name+".go"), []byte(goSource), 0o644)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -67,5 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pqSignatureSchemesEnabled bool
|
||||||
|
|
||||||
var commandGenerateECHKeyPair = &cobra.Command{
|
var commandGenerateECHKeyPair = &cobra.Command{
|
||||||
Use: "ech-keypair <plain_server_name>",
|
Use: "ech-keypair <plain_server_name>",
|
||||||
Short: "Generate TLS ECH key pair",
|
Short: "Generate TLS ECH key pair",
|
||||||
@@ -22,11 +24,12 @@ var commandGenerateECHKeyPair = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes")
|
||||||
commandGenerate.AddCommand(commandGenerateECHKeyPair)
|
commandGenerate.AddCommand(commandGenerateECHKeyPair)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateECHKeyPair(serverName string) error {
|
func generateECHKeyPair(serverName string) error {
|
||||||
configPem, keyPem, err := tls.ECHKeygenDefault(serverName)
|
configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateTLSKeyPair(serverName string) error {
|
func generateTLSKeyPair(serverName string) error {
|
||||||
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||||
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,23 +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.RuleSetVersion5 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.PackageNameRegex) > 0
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion4
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
@@ -57,14 +56,6 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
return E.Cause(err, "read rule-set")
|
||||||
}
|
}
|
||||||
if flagRuleSetMatchFormat == "" {
|
|
||||||
switch filepath.Ext(sourcePath) {
|
|
||||||
case ".json":
|
|
||||||
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
|
||||||
case ".srs":
|
|
||||||
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var ruleSet option.PlainRuleSetCompat
|
var ruleSet option.PlainRuleSetCompat
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
commandNetworkQualityFlagConfigURL string
|
|
||||||
commandNetworkQualityFlagSerial bool
|
|
||||||
commandNetworkQualityFlagMaxRuntime int
|
|
||||||
commandNetworkQualityFlagHTTP3 bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandNetworkQuality = &cobra.Command{
|
|
||||||
Use: "networkquality",
|
|
||||||
Short: "Run a network quality test",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := runNetworkQuality()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandNetworkQuality.Flags().StringVar(
|
|
||||||
&commandNetworkQualityFlagConfigURL,
|
|
||||||
"config-url", "",
|
|
||||||
"Network quality test config URL (default: Apple mensura)",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().BoolVar(
|
|
||||||
&commandNetworkQualityFlagSerial,
|
|
||||||
"serial", false,
|
|
||||||
"Run download and upload tests sequentially instead of in parallel",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().IntVar(
|
|
||||||
&commandNetworkQualityFlagMaxRuntime,
|
|
||||||
"max-runtime", int(networkquality.DefaultMaxRuntime/time.Second),
|
|
||||||
"Network quality maximum runtime in seconds",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().BoolVar(
|
|
||||||
&commandNetworkQualityFlagHTTP3,
|
|
||||||
"http3", false,
|
|
||||||
"Use HTTP/3 (QUIC) for measurement traffic",
|
|
||||||
)
|
|
||||||
commandTools.AddCommand(commandNetworkQuality)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runNetworkQuality() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := networkquality.NewHTTPClient(dialer)
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
|
|
||||||
measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(dialer, commandNetworkQualityFlagHTTP3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "==== NETWORK QUALITY TEST ====")
|
|
||||||
|
|
||||||
result, err := networkquality.Run(networkquality.Options{
|
|
||||||
ConfigURL: commandNetworkQualityFlagConfigURL,
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
NewMeasurementClient: measurementClientFactory,
|
|
||||||
Serial: commandNetworkQualityFlagSerial,
|
|
||||||
MaxRuntime: time.Duration(commandNetworkQualityFlagMaxRuntime) * time.Second,
|
|
||||||
Context: globalCtx,
|
|
||||||
OnProgress: func(p networkquality.Progress) {
|
|
||||||
if !commandNetworkQualityFlagSerial && p.Phase != networkquality.PhaseIdle {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d Upload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM,
|
|
||||||
networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch networkquality.Phase(p.Phase) {
|
|
||||||
case networkquality.PhaseIdle:
|
|
||||||
if p.IdleLatencyMs > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rIdle Latency: %d ms", p.IdleLatencyMs)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, "\rMeasuring idle latency...")
|
|
||||||
}
|
|
||||||
case networkquality.PhaseDownload:
|
|
||||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM)
|
|
||||||
case networkquality.PhaseUpload:
|
|
||||||
fmt.Fprintf(os.Stderr, "\rUpload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintln(os.Stderr, strings.Repeat("-", 40))
|
|
||||||
fmt.Fprintf(os.Stderr, "Idle Latency: %d ms\n", result.IdleLatencyMs)
|
|
||||||
fmt.Fprintf(os.Stderr, "Download Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.DownloadCapacity), result.DownloadCapacityAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Upload Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.UploadCapacity), result.UploadCapacityAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Download Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.DownloadRPM), result.DownloadRPMAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Upload Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.UploadRPM), result.UploadRPMAccuracy)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandSTUNFlagServer string
|
|
||||||
|
|
||||||
var commandSTUN = &cobra.Command{
|
|
||||||
Use: "stun",
|
|
||||||
Short: "Run a STUN test",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := runSTUN()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandSTUN.Flags().StringVarP(&commandSTUNFlagServer, "server", "s", stun.DefaultServer, "STUN server address")
|
|
||||||
commandTools.AddCommand(commandSTUN)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSTUN() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "==== STUN TEST ====")
|
|
||||||
|
|
||||||
result, err := stun.Run(stun.Options{
|
|
||||||
Server: commandSTUNFlagServer,
|
|
||||||
Dialer: dialer,
|
|
||||||
Context: globalCtx,
|
|
||||||
OnProgress: func(p stun.Progress) {
|
|
||||||
switch p.Phase {
|
|
||||||
case stun.PhaseBinding:
|
|
||||||
if p.ExternalAddr != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rExternal Address: %s (%d ms)", p.ExternalAddr, p.LatencyMs)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, "\rSending binding request...")
|
|
||||||
}
|
|
||||||
case stun.PhaseNATMapping:
|
|
||||||
fmt.Fprint(os.Stderr, "\rDetecting NAT mapping behavior...")
|
|
||||||
case stun.PhaseNATFiltering:
|
|
||||||
fmt.Fprint(os.Stderr, "\rDetecting NAT filtering behavior...")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintf(os.Stderr, "External Address: %s\n", result.ExternalAddr)
|
|
||||||
fmt.Fprintf(os.Stderr, "Latency: %d ms\n", result.LatencyMs)
|
|
||||||
if result.NATTypeSupported {
|
|
||||||
fmt.Fprintf(os.Stderr, "NAT Mapping: %s\n", result.NATMapping)
|
|
||||||
fmt.Fprintf(os.Stderr, "NAT Filtering: %s\n", result.NATFiltering)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, "NAT Type Detection: not supported by server")
|
|
||||||
}
|
|
||||||
return 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
|
||||||
@@ -96,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,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
|
||||||
@@ -163,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")
|
||||||
@@ -171,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
|
||||||
@@ -295,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:]
|
||||||
@@ -408,9 +294,11 @@ 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) {
|
||||||
@@ -454,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
|
||||||
|
|||||||
31
common/badtls/read_wait_ech.go
Normal file
31
common/badtls/read_wait_ech.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build go1.21 && !without_badtls && with_ech
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/cloudflare-tls"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||||
|
tlsConn, loaded := common.Cast[*tls.Conn](conn)
|
||||||
|
if !loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return true, func() error {
|
||||||
|
return echReadRecord(tlsConn)
|
||||||
|
}, func() error {
|
||||||
|
return echHandlePostHandshakeMessage(tlsConn)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord
|
||||||
|
func echReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
|
//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage
|
||||||
|
func echHandlePostHandshakeMessage(c *tls.Conn) error
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.25 || !badlinkname
|
//go:build !go1.21 || without_badtls
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
|||||||
31
common/badtls/read_wait_utls.go
Normal file
31
common/badtls/read_wait_utls.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build go1.21 && !without_badtls && with_utls
|
||||||
|
|
||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/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/sagernet/utls.(*Conn).readRecord
|
||||||
|
func utlsReadRecord(c *tls.Conn) error
|
||||||
|
|
||||||
|
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/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,10 +83,6 @@ 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) {
|
||||||
if strings.HasPrefix(versionName, "v") {
|
if strings.HasPrefix(versionName, "v") {
|
||||||
versionName = versionName[1:]
|
versionName = versionName[1:]
|
||||||
|
|||||||
@@ -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,18 +0,0 @@
|
|||||||
// Code generated by 'make update_certificates'. DO NOT EDIT.
|
|
||||||
|
|
||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
_ "embed"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed chrome.pem
|
|
||||||
var chromeIncludedPEM string
|
|
||||||
|
|
||||||
var chromeIncluded *x509.CertPool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
chromeIncluded = x509.NewCertPool()
|
|
||||||
chromeIncluded.AppendCertsFromPEM([]byte(chromeIncludedPEM))
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
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,11 +21,8 @@ import (
|
|||||||
var _ adapter.CertificateStore = (*Store)(nil)
|
var _ adapter.CertificateStore = (*Store)(nil)
|
||||||
|
|
||||||
type Store struct {
|
type Store struct {
|
||||||
access sync.RWMutex
|
|
||||||
store string
|
|
||||||
systemPool *x509.CertPool
|
systemPool *x509.CertPool
|
||||||
currentPool *x509.CertPool
|
currentPool *x509.CertPool
|
||||||
currentPEM []string
|
|
||||||
certificate string
|
certificate string
|
||||||
certificatePaths []string
|
certificatePaths []string
|
||||||
certificateDirectoryPaths []string
|
certificateDirectoryPaths []string
|
||||||
@@ -35,18 +32,17 @@ 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) {
|
||||||
var systemPool *x509.CertPool
|
var systemPool *x509.CertPool
|
||||||
switch options.Store {
|
switch options.Store {
|
||||||
case C.CertificateStoreSystem, "":
|
case C.CertificateStoreSystem:
|
||||||
systemPool = x509.NewCertPool()
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
systemCertificates := platformInterface.SystemCertificates()
|
||||||
var systemValid bool
|
if len(systemCertificates) > 0 {
|
||||||
if platformInterface != nil {
|
systemPool = x509.NewCertPool()
|
||||||
for _, cert := range platformInterface.SystemCertificates() {
|
for _, cert := range systemCertificates {
|
||||||
if systemPool.AppendCertsFromPEM([]byte(cert)) {
|
if !systemPool.AppendCertsFromPEM([]byte(cert)) {
|
||||||
systemValid = true
|
return nil, E.New("invalid system certificate PEM: ", cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if !systemValid {
|
|
||||||
certPool, err := x509.SystemCertPool()
|
certPool, err := x509.SystemCertPool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -55,15 +51,12 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
|
|||||||
}
|
}
|
||||||
case C.CertificateStoreMozilla:
|
case C.CertificateStoreMozilla:
|
||||||
systemPool = mozillaIncluded
|
systemPool = mozillaIncluded
|
||||||
case C.CertificateStoreChrome:
|
|
||||||
systemPool = chromeIncluded
|
|
||||||
case C.CertificateStoreNone:
|
case C.CertificateStoreNone:
|
||||||
systemPool = nil
|
systemPool = nil
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown certificate store: ", options.Store)
|
return nil, E.New("unknown certificate store: ", options.Store)
|
||||||
}
|
}
|
||||||
store := &Store{
|
store := &Store{
|
||||||
store: options.Store,
|
|
||||||
systemPool: systemPool,
|
systemPool: systemPool,
|
||||||
certificate: strings.Join(options.Certificate, "\n"),
|
certificate: strings.Join(options.Certificate, "\n"),
|
||||||
certificatePaths: options.CertificatePath,
|
certificatePaths: options.CertificatePath,
|
||||||
@@ -121,42 +114,20 @@ 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) StoreKind() string {
|
|
||||||
return s.store
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) CurrentPEM() []string {
|
|
||||||
s.access.RLock()
|
|
||||||
defer s.access.RUnlock()
|
|
||||||
return append([]string(nil), s.currentPEM...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) update() error {
|
func (s *Store) update() error {
|
||||||
s.access.Lock()
|
|
||||||
defer s.access.Unlock()
|
|
||||||
var currentPool *x509.CertPool
|
var currentPool *x509.CertPool
|
||||||
var currentPEM []string
|
|
||||||
if s.systemPool == nil {
|
if s.systemPool == nil {
|
||||||
currentPool = x509.NewCertPool()
|
currentPool = x509.NewCertPool()
|
||||||
} else {
|
} else {
|
||||||
currentPool = s.systemPool.Clone()
|
currentPool = s.systemPool.Clone()
|
||||||
}
|
}
|
||||||
switch s.store {
|
|
||||||
case C.CertificateStoreMozilla:
|
|
||||||
currentPEM = append(currentPEM, mozillaIncludedPEM)
|
|
||||||
case C.CertificateStoreChrome:
|
|
||||||
currentPEM = append(currentPEM, chromeIncludedPEM)
|
|
||||||
}
|
|
||||||
if s.certificate != "" {
|
if s.certificate != "" {
|
||||||
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
|
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
|
||||||
return E.New("invalid certificate PEM strings")
|
return E.New("invalid certificate PEM strings")
|
||||||
}
|
}
|
||||||
currentPEM = append(currentPEM, s.certificate)
|
|
||||||
}
|
}
|
||||||
for _, path := range s.certificatePaths {
|
for _, path := range s.certificatePaths {
|
||||||
pemContent, err := os.ReadFile(path)
|
pemContent, err := os.ReadFile(path)
|
||||||
@@ -166,7 +137,6 @@ func (s *Store) update() error {
|
|||||||
if !currentPool.AppendCertsFromPEM(pemContent) {
|
if !currentPool.AppendCertsFromPEM(pemContent) {
|
||||||
return E.New("invalid certificate PEM file: ", path)
|
return E.New("invalid certificate PEM file: ", path)
|
||||||
}
|
}
|
||||||
currentPEM = append(currentPEM, string(pemContent))
|
|
||||||
}
|
}
|
||||||
var firstErr error
|
var firstErr error
|
||||||
for _, directoryPath := range s.certificateDirectoryPaths {
|
for _, directoryPath := range s.certificateDirectoryPaths {
|
||||||
@@ -179,8 +149,8 @@ func (s *Store) update() error {
|
|||||||
}
|
}
|
||||||
for _, directoryEntry := range directoryEntries {
|
for _, directoryEntry := range directoryEntries {
|
||||||
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
|
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
|
||||||
if err == nil && currentPool.AppendCertsFromPEM(pemContent) {
|
if err == nil {
|
||||||
currentPEM = append(currentPEM, string(pemContent))
|
currentPool.AppendCertsFromPEM(pemContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +158,6 @@ func (s *Store) update() error {
|
|||||||
return firstErr
|
return firstErr
|
||||||
}
|
}
|
||||||
s.currentPool = currentPool
|
s.currentPool = currentPool
|
||||||
s.currentPEM = currentPEM
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user