mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-15 05:08:33 +10:00
Compare commits
86 Commits
testing
...
dev-ts-rel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4637f8182d | ||
|
|
b8e441a3c4 | ||
|
|
97e20090cb | ||
|
|
3bbb23ea3b | ||
|
|
7da7601903 | ||
|
|
eaee6bc493 | ||
|
|
b34469986b | ||
|
|
0a88fc6314 | ||
|
|
e984ad753b | ||
|
|
88bda5c306 | ||
|
|
3294a46a28 | ||
|
|
ce285f8d79 | ||
|
|
4e73574144 | ||
|
|
8881882326 | ||
|
|
b675ed2563 | ||
|
|
a44f8c7b5d | ||
|
|
003cf13898 | ||
|
|
2f906adfa1 | ||
|
|
bef0a2f240 | ||
|
|
e27a335ee0 | ||
|
|
a1694d4c7b | ||
|
|
48e5344cea | ||
|
|
6e59a76941 | ||
|
|
6a7264aa91 | ||
|
|
bc23473411 | ||
|
|
8171e792cc | ||
|
|
589e4e5bd7 | ||
|
|
7dd91362b5 | ||
|
|
0e79256f15 | ||
|
|
05169b09ad | ||
|
|
99c125d8f3 | ||
|
|
e473c64cd6 | ||
|
|
be7254c335 | ||
|
|
bf0e432340 | ||
|
|
d592e2d12a | ||
|
|
dd164d9150 | ||
|
|
84b277615c | ||
|
|
13e425d5c3 | ||
|
|
626ef0b427 | ||
|
|
277c643c3e | ||
|
|
7a4c70ede9 | ||
|
|
55df080e2a | ||
|
|
71253f800e | ||
|
|
30ef92ec7b | ||
|
|
5ce866cc8a | ||
|
|
eca6a5da18 | ||
|
|
37a43dd63a | ||
|
|
2f377b2cdf | ||
|
|
fac4068214 | ||
|
|
ee07065f7b | ||
|
|
f70867e0a9 | ||
|
|
bf43a6655e | ||
|
|
bf055b8ae2 | ||
|
|
f88d249f03 | ||
|
|
10d6d22b73 | ||
|
|
65e7649952 | ||
|
|
d01534aa5c | ||
|
|
3efe0fdfdc | ||
|
|
67a0c19b07 | ||
|
|
3546a9368b | ||
|
|
8ab5c7695f | ||
|
|
07190d8d8a | ||
|
|
8e627088c6 | ||
|
|
f306f704bc | ||
|
|
537ca35cfe | ||
|
|
84a0f240f9 | ||
|
|
7f13a66e12 | ||
|
|
301e829266 | ||
|
|
b04310f285 | ||
|
|
8acef05e95 | ||
|
|
6922ec1070 | ||
|
|
9964bc39da | ||
|
|
644cd773c7 | ||
|
|
032e00f38d | ||
|
|
f9a9845901 | ||
|
|
b1fae028ce | ||
|
|
07d9ec4f68 | ||
|
|
61cecf0b01 | ||
|
|
6aa6ee2572 | ||
|
|
591665a302 | ||
|
|
1c2d38fcab | ||
|
|
1357294a63 | ||
|
|
a5135e33fd | ||
|
|
0f772f7bbe | ||
|
|
65f5f406b3 | ||
|
|
96f1f9e205 |
@@ -14,7 +14,6 @@
|
|||||||
--depends kmod-inet-diag
|
--depends kmod-inet-diag
|
||||||
--depends kmod-tun
|
--depends kmod-tun
|
||||||
--depends firewall4
|
--depends firewall4
|
||||||
--depends kmod-nft-queue
|
|
||||||
|
|
||||||
--before-remove release/config/openwrt.prerm
|
--before-remove release/config/openwrt.prerm
|
||||||
|
|
||||||
|
|||||||
23
.fpm_pacman
23
.fpm_pacman
@@ -1,23 +0,0 @@
|
|||||||
-s dir
|
|
||||||
--name sing-box
|
|
||||||
--category net
|
|
||||||
--license GPL-3.0-or-later
|
|
||||||
--description "The universal proxy platform."
|
|
||||||
--url "https://sing-box.sagernet.org/"
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
|
||||||
--config-files etc/sing-box/config.json
|
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
|
||||||
|
|
||||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
|
||||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
|
||||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
|
||||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
--license GPL-3.0-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--vendor SagerNet
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
|
|||||||
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
|||||||
e4926ba205fae5351e3d3eeafff7e7029654424a
|
1cc61ad20399081362ccbc18d650432d1a6d42ec
|
||||||
|
|||||||
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"
|
|
||||||
33
.github/detect_track.sh
vendored
33
.github/detect_track.sh
vendored
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
branches=$(git branch -r --contains HEAD)
|
|
||||||
if echo "$branches" | grep -q 'origin/stable'; then
|
|
||||||
track=stable
|
|
||||||
elif echo "$branches" | grep -q 'origin/testing'; then
|
|
||||||
track=testing
|
|
||||||
elif echo "$branches" | grep -q 'origin/oldstable'; then
|
|
||||||
track=oldstable
|
|
||||||
else
|
|
||||||
echo "ERROR: HEAD is not on any known release branch (stable/testing/oldstable)" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$track" == "stable" ]]; then
|
|
||||||
tag=$(git describe --tags --exact-match HEAD 2>/dev/null || true)
|
|
||||||
if [[ -n "$tag" && "$tag" == *"-"* ]]; then
|
|
||||||
track=beta
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$track" in
|
|
||||||
stable) name=sing-box; docker_tag=latest ;;
|
|
||||||
beta) name=sing-box-beta; docker_tag=latest-beta ;;
|
|
||||||
testing) name=sing-box-testing; docker_tag=latest-testing ;;
|
|
||||||
oldstable) name=sing-box-oldstable; docker_tag=latest-oldstable ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "track=${track} name=${name} docker_tag=${docker_tag}" >&2
|
|
||||||
echo "TRACK=${track}" >> "$GITHUB_ENV"
|
|
||||||
echo "NAME=${name}" >> "$GITHUB_ENV"
|
|
||||||
echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV"
|
|
||||||
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@@ -6,7 +6,7 @@
|
|||||||
":disableRateLimiting"
|
":disableRateLimiting"
|
||||||
],
|
],
|
||||||
"baseBranches": [
|
"baseBranches": [
|
||||||
"unstable"
|
"dev-next"
|
||||||
],
|
],
|
||||||
"golang": {
|
"golang": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
|
|||||||
45
.github/setup_go_for_macos1013.sh
vendored
45
.github/setup_go_for_macos1013.sh
vendored
@@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
VERSION="1.25.9"
|
|
||||||
PATCH_COMMITS=(
|
|
||||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
|
||||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
|
||||||
)
|
|
||||||
CURL_ARGS=(
|
|
||||||
-fL
|
|
||||||
--silent
|
|
||||||
--show-error
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
||||||
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$HOME/go"
|
|
||||||
cd "$HOME/go"
|
|
||||||
wget "https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
tar -xzf "go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
#cp -a go go_bootstrap
|
|
||||||
mv go go_osx
|
|
||||||
cd go_osx
|
|
||||||
|
|
||||||
# these patch URLs only work on golang1.25.x
|
|
||||||
# that means after golang1.26 release it must be changed
|
|
||||||
# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/
|
|
||||||
# revert:
|
|
||||||
# 33d3f603c1: "cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking"
|
|
||||||
# 937368f84e: "crypto/x509: change how we retrieve chains on darwin"
|
|
||||||
|
|
||||||
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
|
||||||
curl "${CURL_ARGS[@]}" "https://github.com/SagerNet/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external
|
|
||||||
# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the
|
|
||||||
# stdlib (crypto/x509) is compiled from patched src automatically.
|
|
||||||
#cd src
|
|
||||||
#GOROOT_BOOTSTRAP="$HOME/go/go_bootstrap" ./make.bash
|
|
||||||
#cd ../..
|
|
||||||
#rm -rf go_bootstrap "go${VERSION}.darwin-arm64.tar.gz"
|
|
||||||
39
.github/setup_go_for_windows7.sh
vendored
39
.github/setup_go_for_windows7.sh
vendored
@@ -1,35 +1,16 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -euo pipefail
|
VERSION="1.25.5"
|
||||||
|
|
||||||
VERSION="1.25.9"
|
mkdir -p $HOME/go
|
||||||
PATCH_COMMITS=(
|
cd $HOME/go
|
||||||
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
|
||||||
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
|
||||||
"a90777dcf692dd2168577853ba743b4338721b06"
|
|
||||||
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
|
|
||||||
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
|
|
||||||
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
|
|
||||||
)
|
|
||||||
CURL_ARGS=(
|
|
||||||
-fL
|
|
||||||
--silent
|
|
||||||
--show-error
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
|
||||||
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "$HOME/go"
|
|
||||||
cd "$HOME/go"
|
|
||||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||||
mv go go_win7
|
mv go go_win7
|
||||||
cd go_win7
|
cd go_win7
|
||||||
|
|
||||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
# these patch URLs only work on golang1.25.x
|
# this patch file only works on golang1.25.x
|
||||||
# that means after golang1.26 release it must be changed
|
# that means after golang1.26 release it must be changed
|
||||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||||
# revert:
|
# revert:
|
||||||
@@ -37,10 +18,10 @@ cd go_win7
|
|||||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
# fixes:
|
|
||||||
# bed309eff415bcb3c77dd4bc3277b682b89a388d: "Fix os.RemoveAll not working on Windows7"
|
|
||||||
# 34b899c2fb39b092db4fa67c4417e41dc046be4b: "Revert \"os: remove 5ms sleep on Windows in (*Process).Wait\""
|
|
||||||
|
|
||||||
for patch_commit in "${PATCH_COMMITS[@]}"; do
|
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||||
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
|
|
||||||
done
|
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||||
|
|||||||
2
.github/update_cronet.sh
vendored
2
.github/update_cronet.sh
vendored
@@ -10,4 +10,4 @@ git -C $PROJECTS/cronet-go fetch origin go
|
|||||||
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
go get -x github.com/sagernet/cronet-go/all@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
||||||
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
go get -x github.com/sagernet/cronet-go@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
|
||||||
go mod tidy
|
go mod tidy
|
||||||
git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION"
|
git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$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"
|
|
||||||
196
.github/workflows/build.yml
vendored
196
.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 }}
|
||||||
@@ -47,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -70,43 +69,35 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { os: linux, arch: amd64, variant: purego, naive: true }
|
- { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" }
|
||||||
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
- { os: linux, arch: amd64, variant: glibc, naive: true }
|
||||||
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, alpine: x86_64, openwrt: "x86_64" }
|
- { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||||
|
|
||||||
- { os: linux, arch: arm64, variant: purego, naive: true }
|
- { os: linux, arch: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
- { os: linux, arch: arm64, variant: glibc, naive: true }
|
- { os: linux, arch: arm64, variant: glibc, naive: true }
|
||||||
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, alpine: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
- { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||||
|
|
||||||
- { os: linux, arch: "386", go386: sse2 }
|
- { os: linux, arch: "386", go386: sse2, openwrt: "i386_pentium4" }
|
||||||
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
|
- { os: linux, arch: "386", variant: glibc, naive: true, go386: sse2 }
|
||||||
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, alpine: x86, openwrt: "i386_pentium4" }
|
- { os: linux, arch: "386", variant: musl, naive: true, go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||||
|
|
||||||
- { os: linux, arch: arm, goarm: "7" }
|
- { os: linux, arch: arm, goarm: "7", openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||||
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
|
- { os: linux, arch: arm, variant: glibc, naive: true, goarm: "7" }
|
||||||
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||||
|
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
|
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
|
|
||||||
- { os: linux, arch: riscv64, naive: true, variant: glibc }
|
|
||||||
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
|
|
||||||
- { os: linux, arch: loong64, naive: true, variant: glibc }
|
|
||||||
- { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" }
|
|
||||||
|
|
||||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||||
- { os: linux, arch: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" }
|
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||||
- { os: linux, arch: mipsle, gomips: softfloat }
|
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||||
- { os: linux, arch: mips64le, gomips: hardfloat }
|
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
- { os: linux, arch: riscv64 }
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||||
- { os: linux, arch: loong64 }
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||||
|
|
||||||
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
|
||||||
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
|
||||||
@@ -121,10 +112,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! matrix.legacy_win7 }}
|
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
|
- name: Setup Go 1.24
|
||||||
|
if: matrix.legacy_go124
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ~1.24.10
|
||||||
- name: Cache Go for Windows 7
|
- name: Cache Go for Windows 7
|
||||||
if: matrix.legacy_win7
|
if: matrix.legacy_win7
|
||||||
id: cache-go-for-windows7
|
id: cache-go-for-windows7
|
||||||
@@ -132,11 +128,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_win7
|
~/go/go_win7
|
||||||
key: go_win7_1258
|
key: go_win7_1255
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Go for Windows 7
|
||||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |-
|
run: |-
|
||||||
.github/setup_go_for_windows7.sh
|
.github/setup_go_for_windows7.sh
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Go for Windows 7
|
||||||
@@ -160,23 +154,14 @@ jobs:
|
|||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
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
|
- name: Cache Chromium toolchain
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
id: cache-chromium-toolchain
|
id: cache-chromium-toolchain
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
~/cronet-go/naiveproxy/src/out/sysroot-build
|
||||||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
|
||||||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
|
||||||
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
- name: Download Chromium toolchain
|
- name: Download Chromium toolchain
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
@@ -205,10 +190,9 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
TAGS="${TAGS},with_naive_outbound"
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
fi
|
||||||
if [[ "${{ matrix.variant }}" == "purego" ]]; then
|
if [[ "${{ matrix.variant }}" == "purego" ]]; then
|
||||||
TAGS="${TAGS},with_purego"
|
TAGS="${TAGS},with_purego"
|
||||||
@@ -216,16 +200,13 @@ jobs:
|
|||||||
TAGS="${TAGS},with_musl"
|
TAGS="${TAGS},with_musl"
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (purego)
|
- name: Build (purego)
|
||||||
if: matrix.variant == 'purego'
|
if: matrix.variant == 'purego'
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -247,7 +228,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@@ -255,8 +236,6 @@ jobs:
|
|||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GO386: ${{ matrix.go386 }}
|
GO386: ${{ matrix.go386 }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build (musl)
|
- name: Build (musl)
|
||||||
if: matrix.variant == 'musl'
|
if: matrix.variant == 'musl'
|
||||||
@@ -264,7 +243,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@@ -272,8 +251,6 @@ jobs:
|
|||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GO386: ${{ matrix.go386 }}
|
GO386: ${{ matrix.go386 }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build (non-variant)
|
- name: Build (non-variant)
|
||||||
if: matrix.os != 'android' && matrix.variant == ''
|
if: matrix.os != 'android' && matrix.variant == ''
|
||||||
@@ -281,7 +258,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -301,7 +278,7 @@ jobs:
|
|||||||
export CXX="${CC}++"
|
export CXX="${CC}++"
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
@@ -375,7 +352,7 @@ jobs:
|
|||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libarchive-tools
|
sudo apt-get install -y libarchive-tools
|
||||||
cp .fpm_pacman .fpm
|
cp .fpm_systemd .fpm
|
||||||
fpm -t pacman \
|
fpm -t pacman \
|
||||||
-v "$PKG_VERSION" \
|
-v "$PKG_VERSION" \
|
||||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||||
@@ -392,34 +369,14 @@ jobs:
|
|||||||
-p "dist/openwrt.deb" \
|
-p "dist/openwrt.deb" \
|
||||||
--architecture all \
|
--architecture all \
|
||||||
dist/sing-box=/usr/bin/sing-box
|
dist/sing-box=/usr/bin/sing-box
|
||||||
|
SUFFIX=""
|
||||||
|
if [[ "${{ matrix.variant }}" == "musl" ]]; then
|
||||||
|
SUFFIX="_musl"
|
||||||
|
fi
|
||||||
for architecture in ${{ matrix.openwrt }}; do
|
for architecture in ${{ matrix.openwrt }}; do
|
||||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}${SUFFIX}.ipk"
|
||||||
done
|
done
|
||||||
rm "dist/openwrt.deb"
|
rm "dist/openwrt.deb"
|
||||||
- name: Install apk-tools
|
|
||||||
if: matrix.openwrt != '' || matrix.alpine != ''
|
|
||||||
run: |-
|
|
||||||
docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c "apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk"
|
|
||||||
- name: Package OpenWrt APK
|
|
||||||
if: matrix.openwrt != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
for architecture in ${{ matrix.openwrt }}; do
|
|
||||||
.github/build_openwrt_apk.sh \
|
|
||||||
"$architecture" \
|
|
||||||
"${{ needs.calculate_version.outputs.version }}" \
|
|
||||||
"dist/sing-box" \
|
|
||||||
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk"
|
|
||||||
done
|
|
||||||
- name: Package Alpine APK
|
|
||||||
if: matrix.alpine != ''
|
|
||||||
run: |-
|
|
||||||
set -xeuo pipefail
|
|
||||||
.github/build_alpine_apk.sh \
|
|
||||||
"${{ matrix.alpine }}" \
|
|
||||||
"${{ needs.calculate_version.outputs.version }}" \
|
|
||||||
"dist/sing-box" \
|
|
||||||
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk"
|
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
@@ -455,36 +412,22 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- { arch: amd64 }
|
- { arch: amd64 }
|
||||||
- { arch: arm64 }
|
- { arch: arm64 }
|
||||||
- { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" }
|
- { arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
if: ${{ ! matrix.legacy_osx }}
|
if: ${{ ! matrix.legacy_go124 }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.3
|
go-version: ^1.25.3
|
||||||
- name: Cache Go for macOS 10.13
|
- name: Setup Go 1.24
|
||||||
if: matrix.legacy_osx
|
if: matrix.legacy_go124
|
||||||
id: cache-go-for-macos1013
|
uses: actions/setup-go@v5
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
with:
|
||||||
path: |
|
go-version: ~1.24.6
|
||||||
~/go/go_osx
|
|
||||||
key: go_osx_1258
|
|
||||||
- name: Setup Go for macOS 10.13
|
|
||||||
if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ github.token }}
|
|
||||||
run: |-
|
|
||||||
.github/setup_go_for_macos1013.sh
|
|
||||||
- name: Setup Go for macOS 10.13
|
|
||||||
if: matrix.legacy_osx
|
|
||||||
run: |-
|
|
||||||
echo "PATH=$HOME/go/go_osx/bin:$PATH" >> $GITHUB_ENV
|
|
||||||
echo "GOROOT=$HOME/go/go_osx" >> $GITHUB_ENV
|
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
run: |-
|
run: |-
|
||||||
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
|
||||||
@@ -492,27 +435,22 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
|
if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then
|
||||||
else
|
TAGS="${TAGS},with_naive_outbound"
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
GOOS: darwin
|
GOOS: darwin
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set name
|
- name: Set name
|
||||||
run: |-
|
run: |-
|
||||||
@@ -565,11 +503,9 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS
|
|
||||||
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
|
go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" `
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
|
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -579,11 +515,9 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
if: ${{ !matrix.naive }}
|
if: ${{ !matrix.naive }}
|
||||||
run: |
|
run: |
|
||||||
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS
|
|
||||||
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
|
go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" `
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
|
-ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" `
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -628,7 +562,7 @@ jobs:
|
|||||||
path: "dist"
|
path: "dist"
|
||||||
build_android:
|
build_android:
|
||||||
name: Build Android
|
name: Build Android
|
||||||
if: (github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android') && github.ref != 'refs/heads/oldstable'
|
if: github.event_name != 'workflow_dispatch' || inputs.build == 'All' || inputs.build == 'Android'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
@@ -641,7 +575,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -664,12 +598,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
|
||||||
@@ -718,7 +652,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
publish_android:
|
publish_android:
|
||||||
name: Publish Android
|
name: Publish Android
|
||||||
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' && 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
|
||||||
@@ -731,7 +665,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -754,12 +688,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
|
||||||
@@ -830,7 +764,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -838,12 +772,12 @@ jobs:
|
|||||||
git tag v${{ needs.calculate_version.outputs.version }} -f
|
git tag v${{ needs.calculate_version.outputs.version }} -f
|
||||||
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
|
||||||
- name: Checkout main branch
|
- name: Checkout main branch
|
||||||
if: matrix.if && github.ref == 'refs/heads/stable' && github.event_name != 'workflow_dispatch'
|
if: matrix.if && github.ref == 'refs/heads/main-next' && github.event_name != 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout main
|
git checkout main
|
||||||
- name: Checkout dev branch
|
- name: Checkout dev branch
|
||||||
if: matrix.if && github.ref == 'refs/heads/testing'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
cd clients/apple
|
cd clients/apple
|
||||||
git checkout dev
|
git checkout dev
|
||||||
@@ -929,7 +863,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
|
||||||
|
|||||||
77
.github/workflows/docker.yml
vendored
77
.github/workflows/docker.yml
vendored
@@ -3,8 +3,8 @@ name: Publish Docker Images
|
|||||||
on:
|
on:
|
||||||
#push:
|
#push:
|
||||||
# branches:
|
# branches:
|
||||||
# - stable
|
# - main-next
|
||||||
# - testing
|
# - dev-next
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -29,12 +29,10 @@ jobs:
|
|||||||
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
|
||||||
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
- { arch: "386", naive: true, docker_platform: "linux/386" }
|
||||||
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
- { arch: arm, goarm: "7", naive: true, docker_platform: "linux/arm/v7" }
|
||||||
- { arch: mipsle, gomips: softfloat, naive: true, docker_platform: "linux/mipsle" }
|
|
||||||
- { arch: riscv64, naive: true, docker_platform: "linux/riscv64" }
|
|
||||||
- { arch: loong64, naive: true, docker_platform: "linux/loong64" }
|
|
||||||
# Non-naive builds
|
# Non-naive builds
|
||||||
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
|
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
|
||||||
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
|
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
|
||||||
|
- { arch: riscv64, docker_platform: "linux/riscv64" }
|
||||||
- { arch: s390x, docker_platform: "linux/s390x" }
|
- { arch: s390x, docker_platform: "linux/s390x" }
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
@@ -55,7 +53,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.4
|
||||||
- name: Clone cronet-go
|
- name: Clone cronet-go
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
@@ -66,23 +64,14 @@ jobs:
|
|||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
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
|
- name: Cache Chromium toolchain
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
id: cache-chromium-toolchain
|
id: cache-chromium-toolchain
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
~/cronet-go/naiveproxy/src/out/sysroot-build
|
||||||
~/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') }}
|
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
- name: Download Chromium toolchain
|
- name: Download Chromium toolchain
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
@@ -104,34 +93,29 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
TAGS="${TAGS},with_naive_outbound,with_musl"
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (naive)
|
- name: Build (naive)
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
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=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
- name: Build (non-naive)
|
- name: Build (non-naive)
|
||||||
if: ${{ ! matrix.naive }}
|
if: ${{ ! matrix.naive }}
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
|
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=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -s -w -buildid= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -164,17 +148,15 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
platform:
|
||||||
- { platform: "linux/amd64" }
|
- linux/amd64
|
||||||
- { platform: "linux/arm/v6" }
|
- linux/arm/v6
|
||||||
- { platform: "linux/arm/v7" }
|
- linux/arm/v7
|
||||||
- { platform: "linux/arm64" }
|
- linux/arm64
|
||||||
- { platform: "linux/386" }
|
- linux/386
|
||||||
# mipsle: no base Docker image available for this platform
|
- linux/ppc64le
|
||||||
- { platform: "linux/ppc64le" }
|
- linux/riscv64
|
||||||
- { platform: "linux/riscv64" }
|
- linux/s390x
|
||||||
- { platform: "linux/s390x" }
|
|
||||||
- { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" }
|
|
||||||
steps:
|
steps:
|
||||||
- name: Get commit to build
|
- name: Get commit to build
|
||||||
id: ref
|
id: ref
|
||||||
@@ -227,8 +209,6 @@ jobs:
|
|||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile.binary
|
file: Dockerfile.binary
|
||||||
build-args: |
|
|
||||||
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
@@ -244,7 +224,6 @@ 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_docker
|
||||||
@@ -259,13 +238,13 @@ 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@v5
|
||||||
with:
|
with:
|
||||||
@@ -285,11 +264,11 @@ jobs:
|
|||||||
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'
|
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 }}
|
||||||
|
|||||||
16
.github/workflows/lint.yml
vendored
16
.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:
|
||||||
@@ -34,7 +32,7 @@ jobs:
|
|||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v8
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: v2.4.0
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
verify: false
|
verify: false
|
||||||
|
|||||||
56
.github/workflows/linux.yml
vendored
56
.github/workflows/linux.yml
vendored
@@ -3,14 +3,19 @@ name: Build Linux Packages
|
|||||||
on:
|
on:
|
||||||
#push:
|
#push:
|
||||||
# branches:
|
# branches:
|
||||||
# - stable
|
# - main-next
|
||||||
# - testing
|
# - dev-next
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: "Version name"
|
description: "Version name"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
forceBeta:
|
||||||
|
description: "Force beta"
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
@@ -29,7 +34,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -56,14 +61,14 @@ jobs:
|
|||||||
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
- { 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: "386", naive: true, debian: i386, rpm: i386 }
|
||||||
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
- { 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)
|
# Non-naive builds (unsupported architectures)
|
||||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||||
|
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||||
|
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||||
|
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
@@ -72,7 +77,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ^1.25.5
|
||||||
- name: Clone cronet-go
|
- name: Clone cronet-go
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
@@ -83,23 +88,14 @@ jobs:
|
|||||||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||||||
git -C ~/cronet-go checkout FETCH_HEAD
|
git -C ~/cronet-go checkout FETCH_HEAD
|
||||||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
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
|
- name: Cache Chromium toolchain
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
id: cache-chromium-toolchain
|
id: cache-chromium-toolchain
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
|
||||||
~/cronet-go/naiveproxy/src/gn/out/
|
~/cronet-go/naiveproxy/src/out/sysroot-build
|
||||||
~/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') }}
|
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||||||
- name: Download Chromium toolchain
|
- name: Download Chromium toolchain
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
@@ -120,30 +116,24 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
|
||||||
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
if [[ "${{ matrix.naive }}" == "true" ]]; then
|
||||||
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
|
TAGS="${TAGS},with_naive_outbound,with_musl"
|
||||||
else
|
|
||||||
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
|
||||||
fi
|
fi
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Set shared ldflags
|
|
||||||
run: |
|
|
||||||
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
|
|
||||||
- name: Build (naive)
|
- name: Build (naive)
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "1"
|
CGO_ENABLED: "1"
|
||||||
GOOS: linux
|
GOOS: linux
|
||||||
GOARCH: ${{ matrix.arch }}
|
GOARCH: ${{ matrix.arch }}
|
||||||
GOARM: ${{ matrix.goarm }}
|
GOARM: ${{ matrix.goarm }}
|
||||||
GOMIPS: ${{ matrix.gomips }}
|
|
||||||
GOMIPS64: ${{ matrix.gomips }}
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Build (non-naive)
|
- name: Build (non-naive)
|
||||||
if: ${{ ! matrix.naive }}
|
if: ${{ ! matrix.naive }}
|
||||||
@@ -151,7 +141,7 @@ jobs:
|
|||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
|
||||||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
|
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: "0"
|
CGO_ENABLED: "0"
|
||||||
@@ -162,8 +152,14 @@ jobs:
|
|||||||
- name: Set mtime
|
- name: Set mtime
|
||||||
run: |-
|
run: |-
|
||||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||||
- name: Detect track
|
- name: Set name
|
||||||
run: bash .github/detect_track.sh
|
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||||
|
- name: Set beta name
|
||||||
|
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
||||||
|
run: |-
|
||||||
|
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||||
- name: Set version
|
- name: Set version
|
||||||
run: |-
|
run: |-
|
||||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -12,9 +12,6 @@
|
|||||||
/*.jar
|
/*.jar
|
||||||
/*.aar
|
/*.aar
|
||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
/experimental/libbox/*.aar
|
|
||||||
/experimental/libbox/*.xcframework/
|
|
||||||
/experimental/libbox/*.nupkg
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
/venv/
|
/venv/
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ run:
|
|||||||
- with_utls
|
- with_utls
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
|
||||||
- with_ccm
|
|
||||||
- with_ocm
|
|
||||||
- badlinkname
|
|
||||||
- tfogo_checklinkname0
|
|
||||||
linters:
|
linters:
|
||||||
default: none
|
default: none
|
||||||
enable:
|
enable:
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
|
&& go build -v -trimpath -tags \
|
||||||
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \
|
||||||
&& 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= -checklinkname=0" \
|
||||||
./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>"
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
ARG BASE_IMAGE=alpine
|
FROM alpine
|
||||||
FROM ${BASE_IMAGE}
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETVARIANT
|
ARG TARGETVARIANT
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& if command -v apk > /dev/null; then \
|
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
|
||||||
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
|
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
|
||||||
ENTRYPOINT ["sing-box"]
|
ENTRYPOINT ["sing-box"]
|
||||||
|
|||||||
117
Makefile
117
Makefile
@@ -1,18 +1,15 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= $(shell cat release/DEFAULT_BUILD_TAGS_OTHERS)
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||||
|
|
||||||
LDFLAGS_SHARED = $(shell cat release/LDFLAGS)
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
||||||
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
|
||||||
|
|
||||||
@@ -44,7 +41,7 @@ fmt_docs:
|
|||||||
go run ./cmd/internal/format_docs
|
go run ./cmd/internal/format_docs
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
go install -v mvdan.cc/gofumpt@v0.8.0
|
||||||
go install -v github.com/daixiang0/gci@latest
|
go install -v github.com/daixiang0/gci@latest
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
@@ -55,7 +52,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/v2/cmd/golangci-lint@v2.4.0
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
@go run ./cmd/internal/protogen
|
@go run ./cmd/internal/protogen
|
||||||
@@ -92,12 +89,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,7 +109,7 @@ build_ios:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFI.xcarchive && \
|
rm -rf build/SFI.xcarchive && \
|
||||||
xcodebuild clean -scheme SFI && \
|
xcodebuild clean -scheme SFI && \
|
||||||
xcodebuild archive -scheme SFI -configuration Release -destination 'generic/platform=iOS' -archivePath build/SFI.xcarchive -allowProvisioningUpdates | 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 && \
|
||||||
@@ -133,7 +130,7 @@ release_ios: build_ios upload_ios_app_store
|
|||||||
build_macos:
|
build_macos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
rm -rf build/SFM.xcarchive && \
|
rm -rf build/SFM.xcarchive && \
|
||||||
xcodebuild archive -scheme SFM -configuration Release -archivePath build/SFM.xcarchive -allowProvisioningUpdates | 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,50 +139,54 @@ 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 && \
|
||||||
@@ -209,12 +210,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 +238,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.10
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.10
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
@@ -260,8 +262,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 +273,3 @@ update:
|
|||||||
git fetch
|
git fetch
|
||||||
git reset FETCH_HEAD --hard
|
git reset FETCH_HEAD --hard
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
|
|
||||||
%:
|
|
||||||
@:
|
|
||||||
|
|||||||
@@ -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,7 +3,6 @@ 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"
|
||||||
@@ -26,8 +25,8 @@ 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)
|
||||||
ClearCache()
|
ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ type DNSQueryOptions struct {
|
|||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
LookupStrategy C.DomainStrategy
|
LookupStrategy C.DomainStrategy
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
DisableOptimisticCache bool
|
|
||||||
RewriteTTL *uint32
|
RewriteTTL *uint32
|
||||||
ClientSubnet netip.Prefix
|
ClientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
@@ -54,7 +52,6 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio
|
|||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: C.DomainStrategy(options.Strategy),
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
DisableCache: options.DisableCache,
|
DisableCache: options.DisableCache,
|
||||||
DisableOptimisticCache: options.DisableOptimisticCache,
|
|
||||||
RewriteTTL: options.RewriteTTL,
|
RewriteTTL: options.RewriteTTL,
|
||||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -66,13 +63,6 @@ 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
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
@@ -82,6 +72,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 (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/observable"
|
"github.com/sagernet/sing/common/observable"
|
||||||
@@ -47,12 +46,6 @@ 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
|
||||||
@@ -75,11 +68,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))
|
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = buffer.Write(s.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -87,11 +76,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))
|
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = buffer.WriteString(s.LastEtag)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -105,12 +90,7 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
contentLength, err := binary.ReadUvarint(reader)
|
err = varbin.Read(reader, binary.BigEndian, &s.Content)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Content = make([]byte, contentLength)
|
|
||||||
_, err = io.ReadFull(reader, s.Content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -120,16 +100,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,8 +9,6 @@ import (
|
|||||||
"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 {
|
||||||
@@ -69,6 +66,9 @@ type InboundContext struct {
|
|||||||
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
|
||||||
@@ -82,13 +82,9 @@ type InboundContext struct {
|
|||||||
FallbackDelay time.Duration
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
DNSResponse *dns.Msg
|
|
||||||
DestinationAddressMatchFromResponse bool
|
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *ConnectionOwner
|
ProcessInfo *ConnectionOwner
|
||||||
SourceMACAddress net.HardwareAddr
|
|
||||||
SourceHostname string
|
|
||||||
QueryType uint16
|
QueryType uint16
|
||||||
FakeIP bool
|
FakeIP bool
|
||||||
|
|
||||||
@@ -108,10 +104,6 @@ type InboundContext struct {
|
|||||||
func (c *InboundContext) ResetRuleCache() {
|
func (c *InboundContext) ResetRuleCache() {
|
||||||
c.IPCIDRMatchSource = false
|
c.IPCIDRMatchSource = false
|
||||||
c.IPCIDRAcceptEmpty = false
|
c.IPCIDRAcceptEmpty = false
|
||||||
c.ResetRuleMatchCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *InboundContext) ResetRuleMatchCache() {
|
|
||||||
c.SourceAddressMatch = false
|
c.SourceAddressMatch = false
|
||||||
c.SourcePortMatch = false
|
c.SourcePortMatch = false
|
||||||
c.DestinationAddressMatch = false
|
c.DestinationAddressMatch = false
|
||||||
@@ -119,51 +111,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 {
|
||||||
|
|||||||
@@ -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,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)
|
|
||||||
}
|
|
||||||
@@ -36,10 +36,6 @@ type PlatformInterface interface {
|
|||||||
|
|
||||||
UsePlatformNotification() bool
|
UsePlatformNotification() bool
|
||||||
SendNotification(notification *Notification) error
|
SendNotification(notification *Notification) error
|
||||||
|
|
||||||
UsePlatformNeighborResolver() bool
|
|
||||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FindConnectionOwnerRequest struct {
|
type FindConnectionOwnerRequest struct {
|
||||||
@@ -55,7 +51,7 @@ type ConnectionOwner struct {
|
|||||||
UserId int32
|
UserId int32
|
||||||
UserName string
|
UserName string
|
||||||
ProcessPath string
|
ProcessPath string
|
||||||
AndroidPackageNames []string
|
AndroidPackageName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ type Router interface {
|
|||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
NeedFindProcess() bool
|
|
||||||
NeedFindNeighbor() bool
|
|
||||||
NeighborResolver() NeighborResolver
|
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
@@ -66,16 +63,10 @@ type RuleSet interface {
|
|||||||
|
|
||||||
type RuleSetUpdateCallback func(it RuleSet)
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type DNSRuleSetUpdateValidator interface {
|
|
||||||
ValidateRuleSetMetadataUpdate(tag string, metadata RuleSetMetadata) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
ContainsDNSQueryTypeRule bool
|
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
type HTTPStartContext struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -20,9 +18,8 @@ type Rule interface {
|
|||||||
|
|
||||||
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,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
|
|
||||||
}
|
|
||||||
92
box.go
92
box.go
@@ -9,7 +9,6 @@ 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"
|
||||||
@@ -20,6 +19,7 @@ import (
|
|||||||
"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/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -45,7 +45,6 @@ type Box struct {
|
|||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
service *boxService.Manager
|
service *boxService.Manager
|
||||||
certificateProvider *boxCertificate.Manager
|
|
||||||
dnsTransport *dns.TransportManager
|
dnsTransport *dns.TransportManager
|
||||||
dnsRouter *dns.Router
|
dnsRouter *dns.Router
|
||||||
connection *route.ConnectionManager
|
connection *route.ConnectionManager
|
||||||
@@ -67,7 +66,6 @@ func Context(
|
|||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
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 {
|
||||||
@@ -92,10 +90,6 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
ctx = service.ContextWith[adapter.ServiceRegistry](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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +106,6 @@ func New(options Options) (*Box, error) {
|
|||||||
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)
|
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")
|
||||||
@@ -129,16 +122,10 @@ func New(options Options) (*Box, error) {
|
|||||||
if serviceRegistry == nil {
|
if serviceRegistry == nil {
|
||||||
return nil, E.New("missing service registry in context")
|
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
|
||||||
@@ -189,19 +176,13 @@ func New(options Options) (*Box, error) {
|
|||||||
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)
|
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)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
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, dnsOptions)
|
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")
|
||||||
@@ -288,24 +269,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 != "" {
|
||||||
@@ -332,22 +295,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 {
|
for i, serviceOptions := range options.Services {
|
||||||
var tag string
|
var tag string
|
||||||
if certificateProviderOptions.Tag != "" {
|
if serviceOptions.Tag != "" {
|
||||||
tag = certificateProviderOptions.Tag
|
tag = serviceOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = certificateProviderManager.Create(
|
err = serviceManager.Create(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
certificateProviderOptions.Type,
|
serviceOptions.Type,
|
||||||
certificateProviderOptions.Options,
|
serviceOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize certificate provider[", i, "]")
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||||
@@ -360,12 +323,11 @@ func New(options Options) (*Box, error) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
||||||
return dnsTransportRegistry.CreateDNSTransport(
|
return local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
C.DNSTypeLocal,
|
option.LocalDNSServerOptions{},
|
||||||
&option.LocalDNSServerOptions{},
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -375,7 +337,7 @@ 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)
|
internalServices = append(internalServices, cacheFile)
|
||||||
}
|
}
|
||||||
@@ -424,7 +386,6 @@ func New(options Options) (*Box, error) {
|
|||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
service: serviceManager,
|
service: serviceManager,
|
||||||
certificateProvider: certificateProviderManager,
|
|
||||||
dnsRouter: dnsRouter,
|
dnsRouter: dnsRouter,
|
||||||
connection: connectionManager,
|
connection: connectionManager,
|
||||||
router: router,
|
router: router,
|
||||||
@@ -486,11 +447,11 @@ func (s *Box) preStart() error {
|
|||||||
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(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -506,19 +467,11 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider)
|
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service)
|
|
||||||
if err != nil {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -526,7 +479,7 @@ func (s *Box) start() error {
|
|||||||
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.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -550,9 +503,8 @@ func (s *Box) Close() error {
|
|||||||
service adapter.Lifecycle
|
service adapter.Lifecycle
|
||||||
}{
|
}{
|
||||||
{"service", s.service},
|
{"service", s.service},
|
||||||
{"inbound", s.inbound},
|
|
||||||
{"certificate-provider", s.certificateProvider},
|
|
||||||
{"endpoint", s.endpoint},
|
{"endpoint", s.endpoint},
|
||||||
|
{"inbound", s.inbound},
|
||||||
{"outbound", s.outbound},
|
{"outbound", s.outbound},
|
||||||
{"router", s.router},
|
{"router", s.router},
|
||||||
{"connection", s.connection},
|
{"connection", s.connection},
|
||||||
@@ -600,10 +552,6 @@ 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 {
|
func (s *Box) LogFactory() log.Factory {
|
||||||
return s.logFactory
|
return s.logFactory
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: ab09918615...fe128a6cd7
Submodule clients/apple updated: ad7434d676...532c140f05
@@ -100,32 +100,11 @@ findVersion:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func publishTestflight(ctx context.Context) error {
|
func publishTestflight(ctx context.Context) error {
|
||||||
if len(os.Args) < 3 {
|
|
||||||
return E.New("platform required: ios, macos, or tvos")
|
|
||||||
}
|
|
||||||
var platform asc.Platform
|
|
||||||
switch os.Args[2] {
|
|
||||||
case "ios":
|
|
||||||
platform = asc.PlatformIOS
|
|
||||||
case "macos":
|
|
||||||
platform = asc.PlatformMACOS
|
|
||||||
case "tvos":
|
|
||||||
platform = asc.PlatformTVOS
|
|
||||||
default:
|
|
||||||
return E.New("unknown platform: ", os.Args[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
tagVersion, err := build_shared.ReadTagVersion()
|
tagVersion, err := build_shared.ReadTagVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
|
|
||||||
releaseNotes := F.ToString("sing-box ", tagVersion.String())
|
|
||||||
if len(os.Args) >= 4 {
|
|
||||||
releaseNotes = strings.Join(os.Args[3:], " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := createClient(20 * time.Minute)
|
client := createClient(20 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
@@ -136,8 +115,27 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
|
||||||
return it.ID
|
return it.ID
|
||||||
})
|
})
|
||||||
|
var platforms []asc.Platform
|
||||||
|
if len(os.Args) == 3 {
|
||||||
|
switch os.Args[2] {
|
||||||
|
case "ios":
|
||||||
|
platforms = []asc.Platform{asc.PlatformIOS}
|
||||||
|
case "macos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformMACOS}
|
||||||
|
case "tvos":
|
||||||
|
platforms = []asc.Platform{asc.PlatformTVOS}
|
||||||
|
default:
|
||||||
|
return E.New("unknown platform: ", os.Args[2])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
platforms = []asc.Platform{
|
||||||
|
asc.PlatformIOS,
|
||||||
|
asc.PlatformMACOS,
|
||||||
|
asc.PlatformTVOS,
|
||||||
|
}
|
||||||
|
}
|
||||||
waitingForProcess := false
|
waitingForProcess := false
|
||||||
|
for _, platform := range platforms {
|
||||||
log.Info(string(platform), " list builds")
|
log.Info(string(platform), " list builds")
|
||||||
for {
|
for {
|
||||||
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
|
||||||
@@ -148,7 +146,6 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
build := builds.Data[0]
|
||||||
log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")")
|
|
||||||
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
@@ -173,7 +170,9 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
|
||||||
log.Info(string(platform), " ", tag, " update localization")
|
log.Info(string(platform), " ", tag, " update localization")
|
||||||
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes))
|
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
|
||||||
|
F.ToString("sing-box ", tagVersion.String()),
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -207,6 +206,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ var (
|
|||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
// withTailscale bool
|
withTailscale bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
// flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -48,7 +48,7 @@ var (
|
|||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
darwinTags []string
|
||||||
// memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
notMemcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
@@ -60,13 +60,12 @@ 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= -checklinkname=0")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0")
|
||||||
|
|
||||||
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_utls", "with_naive_outbound", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
|
||||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
darwinTags = append(darwinTags, "with_dhcp")
|
||||||
// memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
@@ -165,7 +164,7 @@ func buildAndroid() {
|
|||||||
|
|
||||||
// Build main variant (SDK 23)
|
// Build main variant (SDK 23)
|
||||||
mainTags := append([]string{}, sharedTags...)
|
mainTags := append([]string{}, sharedTags...)
|
||||||
// mainTags = append(mainTags, memcTags...)
|
mainTags = append(mainTags, memcTags...)
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
mainTags = append(mainTags, debugTags...)
|
mainTags = append(mainTags, debugTags...)
|
||||||
}
|
}
|
||||||
@@ -177,7 +176,7 @@ func buildAndroid() {
|
|||||||
|
|
||||||
// Build legacy variant (SDK 21, no naive outbound)
|
// Build legacy variant (SDK 21, no naive outbound)
|
||||||
legacyTags := filterTags(sharedTags, "with_naive_outbound")
|
legacyTags := filterTags(sharedTags, "with_naive_outbound")
|
||||||
// legacyTags = append(legacyTags, memcTags...)
|
legacyTags = append(legacyTags, memcTags...)
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
legacyTags = append(legacyTags, debugTags...)
|
legacyTags = append(legacyTags, debugTags...)
|
||||||
}
|
}
|
||||||
@@ -195,7 +194,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{
|
||||||
@@ -205,9 +204,9 @@ func buildApple() {
|
|||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-not-macos=with_low_memory",
|
||||||
}
|
}
|
||||||
//if !withTailscale {
|
if !withTailscale {
|
||||||
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
||||||
//}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, sharedFlags...)
|
args = append(args, sharedFlags...)
|
||||||
@@ -216,9 +215,9 @@ func buildApple() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, darwinTags...)
|
||||||
//if withTailscale {
|
if withTailscale {
|
||||||
// tags = append(tags, memcTags...)
|
tags = append(tags, memcTags...)
|
||||||
//}
|
}
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,11 +82,6 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
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 {
|
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
||||||
len(rule.DefaultInterfaceAddress) > 0
|
len(rule.DefaultInterfaceAddress) > 0
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
54
common/conntrack/conn.go
Normal file
54
common/conntrack/conn.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
element *list.Element[io.Closer]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(conn net.Conn) (net.Conn, error) {
|
||||||
|
connAccess.Lock()
|
||||||
|
element := openConnection.PushBack(conn)
|
||||||
|
connAccess.Unlock()
|
||||||
|
if KillerEnabled {
|
||||||
|
err := KillerCheck()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
element: element,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
if c.element.Value != nil {
|
||||||
|
connAccess.Lock()
|
||||||
|
if c.element.Value != nil {
|
||||||
|
openConnection.Remove(c.element)
|
||||||
|
c.element.Value = nil
|
||||||
|
}
|
||||||
|
connAccess.Unlock()
|
||||||
|
}
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
35
common/conntrack/killer.go
Normal file
35
common/conntrack/killer.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtimeDebug "runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
KillerEnabled bool
|
||||||
|
MemoryLimit uint64
|
||||||
|
killerLastCheck time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
func KillerCheck() error {
|
||||||
|
if !KillerEnabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
nowTime := time.Now()
|
||||||
|
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
killerLastCheck = nowTime
|
||||||
|
if memory.Total() > MemoryLimit {
|
||||||
|
Close()
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
runtimeDebug.FreeOSMemory()
|
||||||
|
}()
|
||||||
|
return E.New("out of memory")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
55
common/conntrack/packet_conn.go
Normal file
55
common/conntrack/packet_conn.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
element *list.Element[io.Closer]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||||
|
connAccess.Lock()
|
||||||
|
element := openConnection.PushBack(conn)
|
||||||
|
connAccess.Unlock()
|
||||||
|
if KillerEnabled {
|
||||||
|
err := KillerCheck()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &PacketConn{
|
||||||
|
PacketConn: conn,
|
||||||
|
element: element,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
if c.element.Value != nil {
|
||||||
|
connAccess.Lock()
|
||||||
|
if c.element.Value != nil {
|
||||||
|
openConnection.Remove(c.element)
|
||||||
|
c.element.Value = nil
|
||||||
|
}
|
||||||
|
connAccess.Unlock()
|
||||||
|
}
|
||||||
|
return c.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return bufio.NewPacketConn(c.PacketConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
47
common/conntrack/track.go
Normal file
47
common/conntrack/track.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package conntrack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
connAccess sync.RWMutex
|
||||||
|
openConnection list.List[io.Closer]
|
||||||
|
)
|
||||||
|
|
||||||
|
func Count() int {
|
||||||
|
if !Enabled {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return openConnection.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func List() []io.Closer {
|
||||||
|
if !Enabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
connAccess.RLock()
|
||||||
|
defer connAccess.RUnlock()
|
||||||
|
connList := make([]io.Closer, 0, openConnection.Len())
|
||||||
|
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||||
|
connList = append(connList, element.Value)
|
||||||
|
}
|
||||||
|
return connList
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
if !Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
connAccess.Lock()
|
||||||
|
defer connAccess.Unlock()
|
||||||
|
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||||
|
common.Close(element.Value)
|
||||||
|
element.Value = nil
|
||||||
|
}
|
||||||
|
openConnection.Init()
|
||||||
|
}
|
||||||
5
common/conntrack/track_disable.go
Normal file
5
common/conntrack/track_disable.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build !with_conntrack
|
||||||
|
|
||||||
|
package conntrack
|
||||||
|
|
||||||
|
const Enabled = false
|
||||||
5
common/conntrack/track_enable.go
Normal file
5
common/conntrack/track_enable.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_conntrack
|
||||||
|
|
||||||
|
package conntrack
|
||||||
|
|
||||||
|
const Enabled = true
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/listener"
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -36,7 +37,6 @@ type DefaultDialer struct {
|
|||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
netns string
|
netns string
|
||||||
connectionManager adapter.ConnectionManager
|
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
defaultNetworkStrategy bool
|
defaultNetworkStrategy bool
|
||||||
@@ -47,7 +47,6 @@ type DefaultDialer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
connectionManager := service.FromContext[adapter.ConnectionManager](ctx)
|
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if defaultOptions.BindInterface != "" && !disableDefaultBind {
|
if defaultOptions.BindInterface != "" {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
@@ -138,21 +137,12 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
||||||
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
||||||
}
|
}
|
||||||
if options.BindAddressNoPort {
|
|
||||||
if !C.IsLinux {
|
|
||||||
return nil, E.New("`bind_address_no_port` is only supported on Linux")
|
|
||||||
}
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.BindAddressNoPort())
|
|
||||||
}
|
|
||||||
if options.ConnectTimeout != 0 {
|
if options.ConnectTimeout != 0 {
|
||||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
if options.DisableTCPKeepAlive {
|
if !options.DisableTCPKeepAlive {
|
||||||
dialer.KeepAlive = -1
|
|
||||||
dialer.KeepAliveConfig.Enable = false
|
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(options.TCPKeepAlive)
|
keepIdle := time.Duration(options.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
@@ -161,11 +151,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if keepInterval == 0 {
|
if keepInterval == 0 {
|
||||||
keepInterval = C.TCPKeepAliveInterval
|
keepInterval = C.TCPKeepAliveInterval
|
||||||
}
|
}
|
||||||
dialer.KeepAliveConfig = net.KeepAliveConfig{
|
dialer.KeepAlive = keepIdle
|
||||||
Enable: true,
|
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval))
|
||||||
Idle: keepIdle,
|
|
||||||
Interval: keepInterval,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
@@ -213,7 +200,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
netns: options.NetNs,
|
netns: options.NetNs,
|
||||||
connectionManager: connectionManager,
|
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
@@ -242,11 +228,11 @@ func setMarkWrapper(networkManager adapter.NetworkManager, mark uint32, isDefaul
|
|||||||
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
func (d *DefaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
|
||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
} else if address.IsDomain() {
|
} else if address.IsFqdn() {
|
||||||
return nil, E.New("domain not resolved")
|
return nil, E.New("domain not resolved")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return d.trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
return trackConn(listener.ListenNetworkNamespace[net.Conn](d.netns, func() (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
@@ -311,12 +297,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
|||||||
if !fastFallback && !isPrimary {
|
if !fastFallback && !isPrimary {
|
||||||
d.networkLastFallback.Store(time.Now())
|
d.networkLastFallback.Store(time.Now())
|
||||||
}
|
}
|
||||||
return d.trackConn(conn, nil)
|
return trackConn(conn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
return trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
||||||
if destination.IsIPv6() {
|
if destination.IsIPv6() {
|
||||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
@@ -332,9 +318,9 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
|
|
||||||
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
|
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
|
||||||
if !destination.Is6() {
|
if !destination.Is6() {
|
||||||
return d.dialer4.Dialer
|
|
||||||
} else {
|
|
||||||
return d.dialer6.Dialer
|
return d.dialer6.Dialer
|
||||||
|
} else {
|
||||||
|
return d.dialer4.Dialer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,23 +354,23 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d.trackPacketConn(packetConn, nil)
|
return trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) WireGuardControl() control.Func {
|
func (d *DefaultDialer) WireGuardControl() control.Func {
|
||||||
return d.udpListener.Control
|
return d.udpListener.Control
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
if d.connectionManager == nil || err != nil {
|
if !conntrack.Enabled || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return d.connectionManager.TrackConn(conn), nil
|
return conntrack.NewConn(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||||
if d.connectionManager == nil || err != nil {
|
if !conntrack.Enabled || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return d.connectionManager.TrackPacketConn(conn), nil
|
return conntrack.NewPacketConn(conn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
DisableCache: dialOptions.DomainResolver.DisableCache,
|
DisableCache: dialOptions.DomainResolver.DisableCache,
|
||||||
DisableOptimisticCache: dialOptions.DomainResolver.DisableOptimisticCache,
|
|
||||||
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
||||||
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||||
}
|
}
|
||||||
@@ -146,7 +145,3 @@ type ParallelNetworkDialer interface {
|
|||||||
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||||
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PacketDialerWithDestination interface {
|
|
||||||
ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !destination.IsDomain() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
@@ -116,7 +116,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !destination.IsDomain() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
@@ -144,7 +144,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !destination.IsDomain() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
@@ -167,7 +167,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !destination.IsDomain() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
package geosite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Old implementation using varbin reflection-based serialization
|
|
||||||
|
|
||||||
func oldWriteString(writer varbin.Writer, value string) error {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldWriteItem(writer varbin.Writer, item Item) error {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.Write(writer, binary.BigEndian, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldReadString(reader varbin.Reader) (string, error) {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.ReadValue[string](reader, binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldReadItem(reader varbin.Reader) (Item, error) {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.ReadValue[Item](reader, binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
}{
|
|
||||||
{"empty", ""},
|
|
||||||
{"single_char", "a"},
|
|
||||||
{"ascii", "example.com"},
|
|
||||||
{"utf8", "测试域名.中国"},
|
|
||||||
{"special_chars", "\x00\xff\n\t"},
|
|
||||||
{"127_bytes", strings.Repeat("x", 127)},
|
|
||||||
{"128_bytes", strings.Repeat("x", 128)},
|
|
||||||
{"16383_bytes", strings.Repeat("x", 16383)},
|
|
||||||
{"16384_bytes", strings.Repeat("x", 16384)},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Old write
|
|
||||||
var oldBuf bytes.Buffer
|
|
||||||
err := oldWriteString(&oldBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err = writeString(&newBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Bytes must match
|
|
||||||
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
|
||||||
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
|
||||||
|
|
||||||
// New write -> old read
|
|
||||||
readBack, err := oldReadString(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// Old write -> new read
|
|
||||||
readBack2, err := readString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.input, readBack2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestItemCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Note: varbin.Write has a bug where struct values (not pointers) don't write their fields
|
|
||||||
// because field.CanSet() returns false for non-addressable values.
|
|
||||||
// The old geosite code passed Item values to varbin.Write, which silently wrote nothing.
|
|
||||||
// The new code correctly writes Type + Value using manual serialization.
|
|
||||||
// This test verifies the new serialization format and round-trip correctness.
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input Item
|
|
||||||
}{
|
|
||||||
{"domain_empty", Item{Type: RuleTypeDomain, Value: ""}},
|
|
||||||
{"domain_normal", Item{Type: RuleTypeDomain, Value: "example.com"}},
|
|
||||||
{"domain_suffix", Item{Type: RuleTypeDomainSuffix, Value: ".example.com"}},
|
|
||||||
{"domain_keyword", Item{Type: RuleTypeDomainKeyword, Value: "google"}},
|
|
||||||
{"domain_regex", Item{Type: RuleTypeDomainRegex, Value: `^.*\.example\.com$`}},
|
|
||||||
{"utf8_domain", Item{Type: RuleTypeDomain, Value: "测试.com"}},
|
|
||||||
{"long_domain", Item{Type: RuleTypeDomainSuffix, Value: strings.Repeat("a", 200) + ".com"}},
|
|
||||||
{"128_bytes_value", Item{Type: RuleTypeDomain, Value: strings.Repeat("x", 128)}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err := newBuf.WriteByte(byte(tc.input.Type))
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = writeString(&newBuf, tc.input.Value)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify format: Type (1 byte) + Value (uvarint len + bytes)
|
|
||||||
require.True(t, len(newBuf.Bytes()) >= 1, "output too short")
|
|
||||||
require.Equal(t, byte(tc.input.Type), newBuf.Bytes()[0], "type byte mismatch")
|
|
||||||
|
|
||||||
// New write -> old read (varbin can read correctly when given addressable target)
|
|
||||||
readBack, err := oldReadItem(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// New write -> new read
|
|
||||||
reader := bufio.NewReader(bytes.NewReader(newBuf.Bytes()))
|
|
||||||
typeByte, err := reader.ReadByte()
|
|
||||||
require.NoError(t, err)
|
|
||||||
value, err := readString(reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.input, Item{Type: ItemType(typeByte), Value: value})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGeositeWriteReadCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input map[string][]Item
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"empty_map",
|
|
||||||
map[string][]Item{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"single_code_empty_items",
|
|
||||||
map[string][]Item{"test": {}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"single_code_single_item",
|
|
||||||
map[string][]Item{"test": {{Type: RuleTypeDomain, Value: "a.com"}}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"single_code_multi_items",
|
|
||||||
map[string][]Item{
|
|
||||||
"test": {
|
|
||||||
{Type: RuleTypeDomain, Value: "a.com"},
|
|
||||||
{Type: RuleTypeDomainSuffix, Value: ".b.com"},
|
|
||||||
{Type: RuleTypeDomainKeyword, Value: "keyword"},
|
|
||||||
{Type: RuleTypeDomainRegex, Value: `^.*$`},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"multi_code",
|
|
||||||
map[string][]Item{
|
|
||||||
"cn": {{Type: RuleTypeDomain, Value: "baidu.com"}, {Type: RuleTypeDomainSuffix, Value: ".cn"}},
|
|
||||||
"us": {{Type: RuleTypeDomain, Value: "google.com"}},
|
|
||||||
"jp": {{Type: RuleTypeDomainSuffix, Value: ".jp"}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"utf8_values",
|
|
||||||
map[string][]Item{
|
|
||||||
"test": {
|
|
||||||
{Type: RuleTypeDomain, Value: "测试.中国"},
|
|
||||||
{Type: RuleTypeDomainSuffix, Value: ".テスト"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"large_items",
|
|
||||||
generateLargeItems(1000),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Write using new implementation
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err := Write(&buf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Read back and verify
|
|
||||||
reader, codes, err := NewReader(bytes.NewReader(buf.Bytes()))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify all codes exist
|
|
||||||
codeSet := make(map[string]bool)
|
|
||||||
for _, code := range codes {
|
|
||||||
codeSet[code] = true
|
|
||||||
}
|
|
||||||
for code := range tc.input {
|
|
||||||
require.True(t, codeSet[code], "missing code: %s", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify items match
|
|
||||||
for code, expectedItems := range tc.input {
|
|
||||||
items, err := reader.Read(code)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedItems, items, "items mismatch for code: %s", code)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateLargeItems(count int) map[string][]Item {
|
|
||||||
items := make([]Item, count)
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
items[i] = Item{
|
|
||||||
Type: ItemType(i % 4),
|
|
||||||
Value: strings.Repeat("x", i%200) + ".com",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map[string][]Item{"large": items}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
@@ -77,7 +78,7 @@ func (r *Reader) readMetadata() error {
|
|||||||
codeIndex uint64
|
codeIndex uint64
|
||||||
codeLength uint64
|
codeLength uint64
|
||||||
)
|
)
|
||||||
code, err = readString(reader)
|
code, err = varbin.ReadValue[string](reader, binary.BigEndian)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -111,17 +112,10 @@ func (r *Reader) Read(code string) ([]Item, error) {
|
|||||||
}
|
}
|
||||||
r.bufferedReader.Reset(r.reader)
|
r.bufferedReader.Reset(r.reader)
|
||||||
itemList := make([]Item, r.domainLength[code])
|
itemList := make([]Item, r.domainLength[code])
|
||||||
for i := range itemList {
|
err = varbin.Read(r.bufferedReader, binary.BigEndian, &itemList)
|
||||||
typeByte, err := r.bufferedReader.ReadByte()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
itemList[i].Type = ItemType(typeByte)
|
|
||||||
itemList[i].Value, err = readString(r.bufferedReader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return itemList, nil
|
return itemList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,18 +135,3 @@ func (r *readCounter) Read(p []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func readString(reader io.ByteReader) (string, error) {
|
|
||||||
length, err := binary.ReadUvarint(reader)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
bytes := make([]byte, length)
|
|
||||||
for i := range bytes {
|
|
||||||
bytes[i], err = reader.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(bytes), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package geosite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
@@ -19,11 +20,7 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
for _, code := range keys {
|
for _, code := range keys {
|
||||||
index[code] = content.Len()
|
index[code] = content.Len()
|
||||||
for _, item := range domains[code] {
|
for _, item := range domains[code] {
|
||||||
err := content.WriteByte(byte(item.Type))
|
err := varbin.Write(content, binary.BigEndian, item)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = writeString(content, item.Value)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -41,7 +38,7 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, code := range keys {
|
for _, code := range keys {
|
||||||
err = writeString(writer, code)
|
err = varbin.Write(writer, binary.BigEndian, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -62,12 +59,3 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeString(writer varbin.Writer, value string) error {
|
|
||||||
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write([]byte(value))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Conn) Read(b []byte) (int, error) {
|
func (c *Conn) Read(b []byte) (int, error) {
|
||||||
@@ -230,7 +229,7 @@ func (c *Conn) readRawRecord() (typ uint8, data []byte, err error) {
|
|||||||
record := c.rawConn.RawInput.Next(recordHeaderLen + n)
|
record := c.rawConn.RawInput.Next(recordHeaderLen + n)
|
||||||
data, typ, err = c.rawConn.In.Decrypt(record)
|
data, typ, err = c.rawConn.In.Decrypt(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = c.rawConn.In.SetErrorLocked(c.sendAlert(*(*uint8)((*[2]unsafe.Pointer)(unsafe.Pointer(&err))[1])))
|
err = c.rawConn.In.SetErrorLocked(c.sendAlert(uint8(err.(tls.AlertError))))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -151,7 +151,6 @@ func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return common.DefaultValue[T](), E.Cause(err, "get current netns")
|
return common.DefaultValue[T](), E.Cause(err, "get current netns")
|
||||||
}
|
}
|
||||||
defer currentNs.Close()
|
|
||||||
defer netns.Set(currentNs)
|
defer netns.Set(currentNs)
|
||||||
var targetNs netns.NsHandle
|
var targetNs netns.NsHandle
|
||||||
if strings.HasPrefix(nameOrPath, "/") {
|
if strings.HasPrefix(nameOrPath, "/") {
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.listenOptions.ReuseAddr {
|
if l.listenOptions.ReuseAddr {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if l.listenOptions.DisableTCPKeepAlive {
|
if !l.listenOptions.DisableTCPKeepAlive {
|
||||||
listenConfig.KeepAlive = -1
|
|
||||||
listenConfig.KeepAliveConfig.Enable = false
|
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
@@ -102,6 +99,8 @@ func (l *Listener) loopTCPIn() {
|
|||||||
}
|
}
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
metadata.InboundDetour = l.listenOptions.Detour
|
metadata.InboundDetour = l.listenOptions.Detour
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundOptions = l.listenOptions.InboundOptions
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
ctx := log.ContextWithNewID(l.ctx)
|
ctx := log.ContextWithNewID(l.ctx)
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
sBufio "github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FormatBitrate(bps int64) string {
|
|
||||||
switch {
|
|
||||||
case bps >= 1_000_000_000:
|
|
||||||
return fmt.Sprintf("%.1f Gbps", float64(bps)/1_000_000_000)
|
|
||||||
case bps >= 1_000_000:
|
|
||||||
return fmt.Sprintf("%.1f Mbps", float64(bps)/1_000_000)
|
|
||||||
case bps >= 1_000:
|
|
||||||
return fmt.Sprintf("%.1f Kbps", float64(bps)/1_000)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%d bps", bps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPClient(dialer N.Dialer) *http.Client {
|
|
||||||
transport := &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: C.TCPTimeout,
|
|
||||||
}
|
|
||||||
if dialer != nil {
|
|
||||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: transport}
|
|
||||||
}
|
|
||||||
|
|
||||||
func baseTransportFromClient(client *http.Client) (*http.Transport, error) {
|
|
||||||
if client == nil {
|
|
||||||
return nil, E.New("http client is nil")
|
|
||||||
}
|
|
||||||
if client.Transport == nil {
|
|
||||||
return http.DefaultTransport.(*http.Transport).Clone(), nil
|
|
||||||
}
|
|
||||||
transport, ok := client.Transport.(*http.Transport)
|
|
||||||
if !ok {
|
|
||||||
return nil, E.New("http client transport must be *http.Transport")
|
|
||||||
}
|
|
||||||
return transport.Clone(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMeasurementClient(
|
|
||||||
baseClient *http.Client,
|
|
||||||
connectEndpoint string,
|
|
||||||
singleConnection bool,
|
|
||||||
disableKeepAlives bool,
|
|
||||||
readCounters []N.CountFunc,
|
|
||||||
writeCounters []N.CountFunc,
|
|
||||||
) (*http.Client, error) {
|
|
||||||
transport, err := baseTransportFromClient(baseClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
transport.DisableCompression = true
|
|
||||||
transport.DisableKeepAlives = disableKeepAlives
|
|
||||||
if singleConnection {
|
|
||||||
transport.MaxConnsPerHost = 1
|
|
||||||
transport.MaxIdleConnsPerHost = 1
|
|
||||||
transport.MaxIdleConns = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
baseDialContext := transport.DialContext
|
|
||||||
if baseDialContext == nil {
|
|
||||||
dialer := &net.Dialer{}
|
|
||||||
baseDialContext = dialer.DialContext
|
|
||||||
}
|
|
||||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if connectEndpoint != "" {
|
|
||||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
|
||||||
}
|
|
||||||
conn, dialErr := baseDialContext(ctx, network, dialAddr)
|
|
||||||
if dialErr != nil {
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
|
||||||
return sBufio.NewCounterConn(conn, readCounters, writeCounters), nil
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
CheckRedirect: baseClient.CheckRedirect,
|
|
||||||
Jar: baseClient.Jar,
|
|
||||||
Timeout: baseClient.Timeout,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MeasurementClientFactory func(
|
|
||||||
connectEndpoint string,
|
|
||||||
singleConnection bool,
|
|
||||||
disableKeepAlives bool,
|
|
||||||
readCounters []N.CountFunc,
|
|
||||||
writeCounters []N.CountFunc,
|
|
||||||
) (*http.Client, error)
|
|
||||||
|
|
||||||
func defaultMeasurementClientFactory(baseClient *http.Client) MeasurementClientFactory {
|
|
||||||
return func(connectEndpoint string, singleConnection, disableKeepAlives bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) {
|
|
||||||
return newMeasurementClient(baseClient, connectEndpoint, singleConnection, disableKeepAlives, readCounters, writeCounters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOptionalHTTP3Factory(dialer N.Dialer, useHTTP3 bool) (MeasurementClientFactory, error) {
|
|
||||||
if !useHTTP3 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return NewHTTP3MeasurementClientFactory(dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rewriteDialAddress(addr string, connectEndpoint string) string {
|
|
||||||
connectEndpoint = strings.TrimSpace(connectEndpoint)
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
endpointHost, endpointPort, err := net.SplitHostPort(connectEndpoint)
|
|
||||||
if err == nil {
|
|
||||||
host = endpointHost
|
|
||||||
if endpointPort != "" {
|
|
||||||
port = endpointPort
|
|
||||||
}
|
|
||||||
} else if connectEndpoint != "" {
|
|
||||||
host = connectEndpoint
|
|
||||||
}
|
|
||||||
return net.JoinHostPort(host, port)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
//go:build with_quic
|
|
||||||
|
|
||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/quic-go/http3"
|
|
||||||
sBufio "github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) {
|
|
||||||
// singleConnection and disableKeepAlives are not applied:
|
|
||||||
// HTTP/3 multiplexes streams over a single QUIC connection by default.
|
|
||||||
return func(connectEndpoint string, _, _ bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) {
|
|
||||||
transport := &http3.Transport{
|
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if connectEndpoint != "" {
|
|
||||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
|
||||||
}
|
|
||||||
destination := M.ParseSocksaddr(dialAddr)
|
|
||||||
var udpConn net.Conn
|
|
||||||
var dialErr error
|
|
||||||
if dialer != nil {
|
|
||||||
udpConn, dialErr = dialer.DialContext(ctx, N.NetworkUDP, destination)
|
|
||||||
} else {
|
|
||||||
var netDialer net.Dialer
|
|
||||||
udpConn, dialErr = netDialer.DialContext(ctx, N.NetworkUDP, destination.String())
|
|
||||||
}
|
|
||||||
if dialErr != nil {
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
wrappedConn := udpConn
|
|
||||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
|
||||||
wrappedConn = sBufio.NewCounterConn(udpConn, readCounters, writeCounters)
|
|
||||||
}
|
|
||||||
packetConn := sBufio.NewUnbindPacketConn(wrappedConn)
|
|
||||||
quicConn, dialErr := quic.DialEarly(ctx, packetConn, udpConn.RemoteAddr(), tlsCfg, cfg)
|
|
||||||
if dialErr != nil {
|
|
||||||
udpConn.Close()
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
return quicConn, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: transport}, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !with_quic
|
|
||||||
|
|
||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) {
|
|
||||||
return nil, C.ErrQUICNotIncluded
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
type Searcher interface {
|
type Searcher interface {
|
||||||
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
|
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
|
||||||
Close() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNotFound = E.New("process not found")
|
var ErrNotFound = E.New("process not found")
|
||||||
@@ -29,7 +28,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if info.UserId != -1 && info.UserName == "" {
|
if info.UserId != -1 {
|
||||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||||
if osUser != nil {
|
if osUser != nil {
|
||||||
info.UserName = osUser.Username
|
info.UserName = osUser.Username
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Searcher = (*androidSearcher)(nil)
|
var _ Searcher = (*androidSearcher)(nil)
|
||||||
@@ -19,30 +18,22 @@ func NewSearcher(config Config) (Searcher, error) {
|
|||||||
return &androidSearcher{config.PackageManager}, nil
|
return &androidSearcher{config.PackageManager}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *androidSearcher) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
family, protocol, err := socketDiagSettings(network, source)
|
_, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, uid, err := querySocketDiagOnce(family, protocol, source)
|
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
appID := uid % 100000
|
|
||||||
var packageNames []string
|
|
||||||
if sharedPackage, loaded := s.packageManager.SharedPackageByID(appID); loaded {
|
|
||||||
packageNames = append(packageNames, sharedPackage)
|
|
||||||
}
|
|
||||||
if packages, loaded := s.packageManager.PackagesByID(appID); loaded {
|
|
||||||
packageNames = append(packageNames, packages...)
|
|
||||||
}
|
|
||||||
packageNames = common.Uniq(packageNames)
|
|
||||||
return &adapter.ConnectionOwner{
|
return &adapter.ConnectionOwner{
|
||||||
UserId: int32(uid),
|
UserId: int32(uid),
|
||||||
AndroidPackageNames: packageNames,
|
AndroidPackageName: sharedPackage,
|
||||||
}, nil
|
}, nil
|
||||||
|
}
|
||||||
|
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
|
||||||
|
return &adapter.ConnectionOwner{
|
||||||
|
UserId: int32(uid),
|
||||||
|
AndroidPackageName: packageName,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &adapter.ConnectionOwner{UserId: int32(uid)}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Searcher = (*darwinSearcher)(nil)
|
var _ Searcher = (*darwinSearcher)(nil)
|
||||||
@@ -20,12 +24,12 @@ func NewSearcher(_ Config) (Searcher, error) {
|
|||||||
return &darwinSearcher{}, nil
|
return &darwinSearcher{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *darwinSearcher) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
func (d *darwinSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
return FindDarwinConnectionOwner(network, source, destination)
|
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &adapter.ConnectionOwner{ProcessPath: processName, UserId: -1}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var structSize = func() int {
|
var structSize = func() int {
|
||||||
@@ -43,3 +47,107 @@ var structSize = func() int {
|
|||||||
return 384
|
return 384
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||||
|
var spath string
|
||||||
|
switch network {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
spath = "net.inet.tcp.pcblist_n"
|
||||||
|
case N.NetworkUDP:
|
||||||
|
spath = "net.inet.udp.pcblist_n"
|
||||||
|
default:
|
||||||
|
return "", os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
isIPv4 := ip.Is4()
|
||||||
|
|
||||||
|
value, err := unix.SysctlRaw(spath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := value
|
||||||
|
|
||||||
|
// from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n
|
||||||
|
// size/offset are round up (aligned) to 8 bytes in darwin
|
||||||
|
// rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) +
|
||||||
|
// 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n))
|
||||||
|
itemSize := structSize
|
||||||
|
if network == N.NetworkTCP {
|
||||||
|
// rup8(sizeof(xtcpcb_n))
|
||||||
|
itemSize += 208
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackUDPProcess string
|
||||||
|
// skip the first xinpgen(24 bytes) block
|
||||||
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
|
// offset of xinpcb_n and xsocket_n
|
||||||
|
inp, so := i, i+104
|
||||||
|
|
||||||
|
srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20])
|
||||||
|
if uint16(port) != srcPort {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// xinpcb_n.inp_vflag
|
||||||
|
flag := buf[inp+44]
|
||||||
|
|
||||||
|
var srcIP netip.Addr
|
||||||
|
srcIsIPv4 := false
|
||||||
|
switch {
|
||||||
|
case flag&0x1 > 0 && isIPv4:
|
||||||
|
// ipv4
|
||||||
|
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
|
||||||
|
srcIsIPv4 = true
|
||||||
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
|
// ipv6
|
||||||
|
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip == srcIP {
|
||||||
|
// xsocket_n.so_last_pid
|
||||||
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
|
return getExecPathFromPID(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// udp packet connection may be not equal with srcIP
|
||||||
|
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
||||||
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
|
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
||||||
|
return fallbackUDPProcess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExecPathFromPID(pid uint32) (string, error) {
|
||||||
|
const (
|
||||||
|
procpidpathinfo = 0xb
|
||||||
|
procpidpathinfosize = 1024
|
||||||
|
proccallnumpidinfo = 0x2
|
||||||
|
)
|
||||||
|
buf := make([]byte, procpidpathinfosize)
|
||||||
|
_, _, errno := syscall.Syscall6(
|
||||||
|
syscall.SYS_PROC_INFO,
|
||||||
|
proccallnumpidinfo,
|
||||||
|
uintptr(pid),
|
||||||
|
procpidpathinfo,
|
||||||
|
0,
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
procpidpathinfosize)
|
||||||
|
if errno != 0 {
|
||||||
|
return "", errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.ByteSliceToString(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNativeUint32(b []byte) uint32 {
|
||||||
|
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,269 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package process
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
darwinSnapshotTTL = 200 * time.Millisecond
|
|
||||||
|
|
||||||
darwinXinpgenSize = 24
|
|
||||||
darwinXsocketOffset = 104
|
|
||||||
darwinXinpcbForeignPort = 16
|
|
||||||
darwinXinpcbLocalPort = 18
|
|
||||||
darwinXinpcbVFlag = 44
|
|
||||||
darwinXinpcbForeignAddr = 48
|
|
||||||
darwinXinpcbLocalAddr = 64
|
|
||||||
darwinXinpcbIPv4Addr = 12
|
|
||||||
darwinXsocketUID = 64
|
|
||||||
darwinXsocketLastPID = 68
|
|
||||||
darwinTCPExtraStructSize = 208
|
|
||||||
)
|
|
||||||
|
|
||||||
type darwinConnectionEntry struct {
|
|
||||||
localAddr netip.Addr
|
|
||||||
remoteAddr netip.Addr
|
|
||||||
localPort uint16
|
|
||||||
remotePort uint16
|
|
||||||
pid uint32
|
|
||||||
uid int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type darwinConnectionMatchKind uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
darwinConnectionMatchExact darwinConnectionMatchKind = iota
|
|
||||||
darwinConnectionMatchLocalFallback
|
|
||||||
darwinConnectionMatchWildcardFallback
|
|
||||||
)
|
|
||||||
|
|
||||||
type darwinSnapshot struct {
|
|
||||||
createdAt time.Time
|
|
||||||
entries []darwinConnectionEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
type darwinConnectionFinder struct {
|
|
||||||
access sync.Mutex
|
|
||||||
ttl time.Duration
|
|
||||||
snapshots map[string]darwinSnapshot
|
|
||||||
builder func(string) (darwinSnapshot, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sharedDarwinConnectionFinder = newDarwinConnectionFinder(darwinSnapshotTTL)
|
|
||||||
|
|
||||||
func newDarwinConnectionFinder(ttl time.Duration) *darwinConnectionFinder {
|
|
||||||
return &darwinConnectionFinder{
|
|
||||||
ttl: ttl,
|
|
||||||
snapshots: make(map[string]darwinSnapshot),
|
|
||||||
builder: buildDarwinSnapshot,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindDarwinConnectionOwner(network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
|
||||||
return sharedDarwinConnectionFinder.find(network, source, destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *darwinConnectionFinder) find(network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
|
||||||
networkName := N.NetworkName(network)
|
|
||||||
source = normalizeDarwinAddrPort(source)
|
|
||||||
destination = normalizeDarwinAddrPort(destination)
|
|
||||||
var lastOwner *adapter.ConnectionOwner
|
|
||||||
for attempt := 0; attempt < 2; attempt++ {
|
|
||||||
snapshot, fromCache, err := f.loadSnapshot(networkName, attempt > 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entry, matchKind, err := matchDarwinConnectionEntry(snapshot.entries, networkName, source, destination)
|
|
||||||
if err != nil {
|
|
||||||
if err == ErrNotFound && fromCache {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if fromCache && matchKind != darwinConnectionMatchExact {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
owner := &adapter.ConnectionOwner{
|
|
||||||
UserId: entry.uid,
|
|
||||||
}
|
|
||||||
lastOwner = owner
|
|
||||||
if entry.pid == 0 {
|
|
||||||
return owner, nil
|
|
||||||
}
|
|
||||||
processPath, err := getExecPathFromPID(entry.pid)
|
|
||||||
if err == nil {
|
|
||||||
owner.ProcessPath = processPath
|
|
||||||
return owner, nil
|
|
||||||
}
|
|
||||||
if fromCache {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return owner, nil
|
|
||||||
}
|
|
||||||
if lastOwner != nil {
|
|
||||||
return lastOwner, nil
|
|
||||||
}
|
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *darwinConnectionFinder) loadSnapshot(network string, forceRefresh bool) (darwinSnapshot, bool, error) {
|
|
||||||
f.access.Lock()
|
|
||||||
defer f.access.Unlock()
|
|
||||||
if !forceRefresh {
|
|
||||||
if snapshot, loaded := f.snapshots[network]; loaded && time.Since(snapshot.createdAt) < f.ttl {
|
|
||||||
return snapshot, true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
snapshot, err := f.builder(network)
|
|
||||||
if err != nil {
|
|
||||||
return darwinSnapshot{}, false, err
|
|
||||||
}
|
|
||||||
f.snapshots[network] = snapshot
|
|
||||||
return snapshot, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildDarwinSnapshot(network string) (darwinSnapshot, error) {
|
|
||||||
spath, itemSize, err := darwinSnapshotSettings(network)
|
|
||||||
if err != nil {
|
|
||||||
return darwinSnapshot{}, err
|
|
||||||
}
|
|
||||||
value, err := unix.SysctlRaw(spath)
|
|
||||||
if err != nil {
|
|
||||||
return darwinSnapshot{}, err
|
|
||||||
}
|
|
||||||
return darwinSnapshot{
|
|
||||||
createdAt: time.Now(),
|
|
||||||
entries: parseDarwinSnapshot(value, itemSize),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinSnapshotSettings(network string) (string, int, error) {
|
|
||||||
itemSize := structSize
|
|
||||||
switch network {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
return "net.inet.tcp.pcblist_n", itemSize + darwinTCPExtraStructSize, nil
|
|
||||||
case N.NetworkUDP:
|
|
||||||
return "net.inet.udp.pcblist_n", itemSize, nil
|
|
||||||
default:
|
|
||||||
return "", 0, os.ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDarwinSnapshot(buf []byte, itemSize int) []darwinConnectionEntry {
|
|
||||||
entries := make([]darwinConnectionEntry, 0, (len(buf)-darwinXinpgenSize)/itemSize)
|
|
||||||
for i := darwinXinpgenSize; i+itemSize <= len(buf); i += itemSize {
|
|
||||||
inp := i
|
|
||||||
so := i + darwinXsocketOffset
|
|
||||||
entry, ok := parseDarwinConnectionEntry(buf[inp:so], buf[so:so+structSize-darwinXsocketOffset])
|
|
||||||
if ok {
|
|
||||||
entries = append(entries, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDarwinConnectionEntry(inp []byte, so []byte) (darwinConnectionEntry, bool) {
|
|
||||||
if len(inp) < darwinXsocketOffset || len(so) < structSize-darwinXsocketOffset {
|
|
||||||
return darwinConnectionEntry{}, false
|
|
||||||
}
|
|
||||||
entry := darwinConnectionEntry{
|
|
||||||
remotePort: binary.BigEndian.Uint16(inp[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2]),
|
|
||||||
localPort: binary.BigEndian.Uint16(inp[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2]),
|
|
||||||
pid: binary.NativeEndian.Uint32(so[darwinXsocketLastPID : darwinXsocketLastPID+4]),
|
|
||||||
uid: int32(binary.NativeEndian.Uint32(so[darwinXsocketUID : darwinXsocketUID+4])),
|
|
||||||
}
|
|
||||||
flag := inp[darwinXinpcbVFlag]
|
|
||||||
switch {
|
|
||||||
case flag&0x1 != 0:
|
|
||||||
entry.remoteAddr = netip.AddrFrom4([4]byte(inp[darwinXinpcbForeignAddr+darwinXinpcbIPv4Addr : darwinXinpcbForeignAddr+darwinXinpcbIPv4Addr+4]))
|
|
||||||
entry.localAddr = netip.AddrFrom4([4]byte(inp[darwinXinpcbLocalAddr+darwinXinpcbIPv4Addr : darwinXinpcbLocalAddr+darwinXinpcbIPv4Addr+4]))
|
|
||||||
return entry, true
|
|
||||||
case flag&0x2 != 0:
|
|
||||||
entry.remoteAddr = netip.AddrFrom16([16]byte(inp[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16]))
|
|
||||||
entry.localAddr = netip.AddrFrom16([16]byte(inp[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16]))
|
|
||||||
return entry, true
|
|
||||||
default:
|
|
||||||
return darwinConnectionEntry{}, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchDarwinConnectionEntry(entries []darwinConnectionEntry, network string, source netip.AddrPort, destination netip.AddrPort) (darwinConnectionEntry, darwinConnectionMatchKind, error) {
|
|
||||||
sourceAddr := source.Addr()
|
|
||||||
if !sourceAddr.IsValid() {
|
|
||||||
return darwinConnectionEntry{}, darwinConnectionMatchExact, os.ErrInvalid
|
|
||||||
}
|
|
||||||
var localFallback darwinConnectionEntry
|
|
||||||
var hasLocalFallback bool
|
|
||||||
var wildcardFallback darwinConnectionEntry
|
|
||||||
var hasWildcardFallback bool
|
|
||||||
for _, entry := range entries {
|
|
||||||
if entry.localPort != source.Port() || sourceAddr.BitLen() != entry.localAddr.BitLen() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if entry.localAddr == sourceAddr && destination.IsValid() && entry.remotePort == destination.Port() && entry.remoteAddr == destination.Addr() {
|
|
||||||
return entry, darwinConnectionMatchExact, nil
|
|
||||||
}
|
|
||||||
if !destination.IsValid() && entry.localAddr == sourceAddr {
|
|
||||||
return entry, darwinConnectionMatchExact, nil
|
|
||||||
}
|
|
||||||
if network != N.NetworkUDP {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !hasLocalFallback && entry.localAddr == sourceAddr {
|
|
||||||
hasLocalFallback = true
|
|
||||||
localFallback = entry
|
|
||||||
}
|
|
||||||
if !hasWildcardFallback && entry.localAddr.IsUnspecified() {
|
|
||||||
hasWildcardFallback = true
|
|
||||||
wildcardFallback = entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasLocalFallback {
|
|
||||||
return localFallback, darwinConnectionMatchLocalFallback, nil
|
|
||||||
}
|
|
||||||
if hasWildcardFallback {
|
|
||||||
return wildcardFallback, darwinConnectionMatchWildcardFallback, nil
|
|
||||||
}
|
|
||||||
return darwinConnectionEntry{}, darwinConnectionMatchExact, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeDarwinAddrPort(addrPort netip.AddrPort) netip.AddrPort {
|
|
||||||
if !addrPort.IsValid() {
|
|
||||||
return addrPort
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExecPathFromPID(pid uint32) (string, error) {
|
|
||||||
const (
|
|
||||||
procpidpathinfo = 0xb
|
|
||||||
procpidpathinfosize = 1024
|
|
||||||
proccallnumpidinfo = 0x2
|
|
||||||
)
|
|
||||||
buf := make([]byte, procpidpathinfosize)
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
syscall.SYS_PROC_INFO,
|
|
||||||
proccallnumpidinfo,
|
|
||||||
uintptr(pid),
|
|
||||||
procpidpathinfo,
|
|
||||||
0,
|
|
||||||
uintptr(unsafe.Pointer(&buf[0])),
|
|
||||||
procpidpathinfosize)
|
|
||||||
if errno != 0 {
|
|
||||||
return "", errno
|
|
||||||
}
|
|
||||||
return unix.ByteSliceToString(buf), nil
|
|
||||||
}
|
|
||||||
@@ -4,82 +4,33 @@ package process
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Searcher = (*linuxSearcher)(nil)
|
var _ Searcher = (*linuxSearcher)(nil)
|
||||||
|
|
||||||
type linuxSearcher struct {
|
type linuxSearcher struct {
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
diagConns [4]*socketDiagConn
|
|
||||||
processPathCache *uidProcessPathCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSearcher(config Config) (Searcher, error) {
|
func NewSearcher(config Config) (Searcher, error) {
|
||||||
searcher := &linuxSearcher{
|
return &linuxSearcher{config.Logger}, nil
|
||||||
logger: config.Logger,
|
|
||||||
processPathCache: newUIDProcessPathCache(time.Second),
|
|
||||||
}
|
|
||||||
for _, family := range []uint8{syscall.AF_INET, syscall.AF_INET6} {
|
|
||||||
for _, protocol := range []uint8{syscall.IPPROTO_TCP, syscall.IPPROTO_UDP} {
|
|
||||||
searcher.diagConns[socketDiagConnIndex(family, protocol)] = newSocketDiagConn(family, protocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return searcher, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *linuxSearcher) Close() error {
|
|
||||||
var errs []error
|
|
||||||
for _, conn := range s.diagConns {
|
|
||||||
if conn == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
errs = append(errs, conn.Close())
|
|
||||||
}
|
|
||||||
return E.Errors(errs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
inode, uid, err := s.resolveSocketByNetlink(network, source, destination)
|
inode, uid, err := resolveSocketByNetlink(network, source, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
processInfo := &adapter.ConnectionOwner{
|
processPath, err := resolveProcessNameByProcSearch(inode, uid)
|
||||||
UserId: int32(uid),
|
|
||||||
}
|
|
||||||
processPath, err := s.processPathCache.findProcessPath(inode, uid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.DebugContext(ctx, "find process path: ", err)
|
s.logger.DebugContext(ctx, "find process path: ", err)
|
||||||
} else {
|
|
||||||
processInfo.ProcessPath = processPath
|
|
||||||
}
|
}
|
||||||
return processInfo, nil
|
return &adapter.ConnectionOwner{
|
||||||
}
|
UserId: int32(uid),
|
||||||
|
ProcessPath: processPath,
|
||||||
func (s *linuxSearcher) resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
|
}, nil
|
||||||
family, protocol, err := socketDiagSettings(network, source)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
conn := s.diagConns[socketDiagConnIndex(family, protocol)]
|
|
||||||
if conn == nil {
|
|
||||||
return 0, 0, E.New("missing socket diag connection for family=", family, " protocol=", protocol)
|
|
||||||
}
|
|
||||||
if destination.IsValid() && source.Addr().BitLen() == destination.Addr().BitLen() {
|
|
||||||
inode, uid, err = conn.query(source, destination)
|
|
||||||
if err == nil {
|
|
||||||
return inode, uid, nil
|
|
||||||
}
|
|
||||||
if !errors.Is(err, ErrNotFound) {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return querySocketDiagOnce(family, protocol, source)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,67 +3,43 @@
|
|||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
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/contrab/freelru"
|
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62
|
||||||
|
var nativeEndian = func() binary.ByteOrder {
|
||||||
|
var x uint32 = 0x01020304
|
||||||
|
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||||
|
return binary.BigEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
return binary.LittleEndian
|
||||||
|
}()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sizeOfSocketDiagRequestData = 56
|
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
|
||||||
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + sizeOfSocketDiagRequestData
|
|
||||||
socketDiagResponseMinSize = 72
|
|
||||||
socketDiagByFamily = 20
|
socketDiagByFamily = 20
|
||||||
pathProc = "/proc"
|
pathProc = "/proc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type socketDiagConn struct {
|
func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
|
||||||
access sync.Mutex
|
var family uint8
|
||||||
family uint8
|
var protocol uint8
|
||||||
protocol uint8
|
|
||||||
fd int
|
|
||||||
}
|
|
||||||
|
|
||||||
type uidProcessPathCache struct {
|
|
||||||
cache freelru.Cache[uint32, *uidProcessPaths]
|
|
||||||
}
|
|
||||||
|
|
||||||
type uidProcessPaths struct {
|
|
||||||
entries map[uint32]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSocketDiagConn(family, protocol uint8) *socketDiagConn {
|
|
||||||
return &socketDiagConn{
|
|
||||||
family: family,
|
|
||||||
protocol: protocol,
|
|
||||||
fd: -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func socketDiagConnIndex(family, protocol uint8) int {
|
|
||||||
index := 0
|
|
||||||
if protocol == syscall.IPPROTO_UDP {
|
|
||||||
index += 2
|
|
||||||
}
|
|
||||||
if family == syscall.AF_INET6 {
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
func socketDiagSettings(network string, source netip.AddrPort) (family, protocol uint8, err error) {
|
|
||||||
switch network {
|
switch network {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
protocol = syscall.IPPROTO_TCP
|
protocol = syscall.IPPROTO_TCP
|
||||||
@@ -72,308 +48,151 @@ func socketDiagSettings(network string, source netip.AddrPort) (family, protocol
|
|||||||
default:
|
default:
|
||||||
return 0, 0, os.ErrInvalid
|
return 0, 0, os.ErrInvalid
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case source.Addr().Is4():
|
if source.Addr().Is4() {
|
||||||
family = syscall.AF_INET
|
family = syscall.AF_INET
|
||||||
case source.Addr().Is6():
|
} else {
|
||||||
family = syscall.AF_INET6
|
family = syscall.AF_INET6
|
||||||
default:
|
|
||||||
return 0, 0, os.ErrInvalid
|
|
||||||
}
|
}
|
||||||
return family, protocol, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUIDProcessPathCache(ttl time.Duration) *uidProcessPathCache {
|
req := packSocketDiagRequest(family, protocol, source)
|
||||||
cache := common.Must1(freelru.NewSharded[uint32, *uidProcessPaths](64, maphash.NewHasher[uint32]().Hash32))
|
|
||||||
cache.SetLifetime(ttl)
|
|
||||||
return &uidProcessPathCache{cache: cache}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *uidProcessPathCache) findProcessPath(targetInode, uid uint32) (string, error) {
|
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
|
||||||
if cached, ok := c.cache.Get(uid); ok {
|
|
||||||
if processPath, found := cached.entries[targetInode]; found {
|
|
||||||
return processPath, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processPaths, err := buildProcessPathByUIDCache(uid)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
c.cache.Add(uid, &uidProcessPaths{entries: processPaths})
|
|
||||||
processPath, found := processPaths[targetInode]
|
|
||||||
if !found {
|
|
||||||
return "", E.New("process of uid(", uid, "), inode(", targetInode, ") not found")
|
|
||||||
}
|
|
||||||
return processPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socketDiagConn) Close() error {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
return c.closeLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socketDiagConn) query(source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
request := packSocketDiagRequest(c.family, c.protocol, source, destination, false)
|
|
||||||
for attempt := 0; attempt < 2; attempt++ {
|
|
||||||
err = c.ensureOpenLocked()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, E.Cause(err, "dial netlink")
|
return 0, 0, E.Cause(err, "dial netlink")
|
||||||
}
|
}
|
||||||
inode, uid, err = querySocketDiag(c.fd, request)
|
defer syscall.Close(socket)
|
||||||
if err == nil || errors.Is(err, ErrNotFound) {
|
|
||||||
return inode, uid, err
|
|
||||||
}
|
|
||||||
if !shouldRetrySocketDiag(err) {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
_ = c.closeLocked()
|
|
||||||
}
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func querySocketDiagOnce(family, protocol uint8, source netip.AddrPort) (inode, uid uint32, err error) {
|
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
|
||||||
fd, err := openSocketDiag()
|
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
|
||||||
if err != nil {
|
|
||||||
return 0, 0, E.Cause(err, "dial netlink")
|
|
||||||
}
|
|
||||||
defer syscall.Close(fd)
|
|
||||||
return querySocketDiag(fd, packSocketDiagRequest(family, protocol, source, netip.AddrPort{}, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socketDiagConn) ensureOpenLocked() error {
|
err = syscall.Connect(socket, &syscall.SockaddrNetlink{
|
||||||
if c.fd != -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
fd, err := openSocketDiag()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.fd = fd
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func openSocketDiag() (int, error) {
|
|
||||||
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM|syscall.SOCK_CLOEXEC, syscall.NETLINK_INET_DIAG)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
timeout := &syscall.Timeval{Usec: 100}
|
|
||||||
if err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, timeout); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
if err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, timeout); err != nil {
|
|
||||||
syscall.Close(fd)
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
if err = syscall.Connect(fd, &syscall.SockaddrNetlink{
|
|
||||||
Family: syscall.AF_NETLINK,
|
Family: syscall.AF_NETLINK,
|
||||||
|
Pad: 0,
|
||||||
Pid: 0,
|
Pid: 0,
|
||||||
Groups: 0,
|
Groups: 0,
|
||||||
}); err != nil {
|
})
|
||||||
syscall.Close(fd)
|
if err != nil {
|
||||||
return -1, err
|
return
|
||||||
}
|
}
|
||||||
return fd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socketDiagConn) closeLocked() error {
|
_, err = syscall.Write(socket, req)
|
||||||
if c.fd == -1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := syscall.Close(c.fd)
|
|
||||||
c.fd = -1
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort, destination netip.AddrPort, dump bool) []byte {
|
|
||||||
request := make([]byte, sizeOfSocketDiagRequest)
|
|
||||||
|
|
||||||
binary.NativeEndian.PutUint32(request[0:4], sizeOfSocketDiagRequest)
|
|
||||||
binary.NativeEndian.PutUint16(request[4:6], socketDiagByFamily)
|
|
||||||
flags := uint16(syscall.NLM_F_REQUEST)
|
|
||||||
if dump {
|
|
||||||
flags |= syscall.NLM_F_DUMP
|
|
||||||
}
|
|
||||||
binary.NativeEndian.PutUint16(request[6:8], flags)
|
|
||||||
binary.NativeEndian.PutUint32(request[8:12], 0)
|
|
||||||
binary.NativeEndian.PutUint32(request[12:16], 0)
|
|
||||||
|
|
||||||
request[16] = family
|
|
||||||
request[17] = protocol
|
|
||||||
request[18] = 0
|
|
||||||
request[19] = 0
|
|
||||||
if dump {
|
|
||||||
binary.NativeEndian.PutUint32(request[20:24], 0xFFFFFFFF)
|
|
||||||
}
|
|
||||||
requestSource := source
|
|
||||||
requestDestination := destination
|
|
||||||
if protocol == syscall.IPPROTO_UDP && !dump && destination.IsValid() {
|
|
||||||
// udp_dump_one expects the exact-match endpoints reversed for historical reasons.
|
|
||||||
requestSource, requestDestination = destination, source
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint16(request[24:26], requestSource.Port())
|
|
||||||
binary.BigEndian.PutUint16(request[26:28], requestDestination.Port())
|
|
||||||
if family == syscall.AF_INET6 {
|
|
||||||
copy(request[28:44], requestSource.Addr().AsSlice())
|
|
||||||
if requestDestination.IsValid() {
|
|
||||||
copy(request[44:60], requestDestination.Addr().AsSlice())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
copy(request[28:32], requestSource.Addr().AsSlice())
|
|
||||||
if requestDestination.IsValid() {
|
|
||||||
copy(request[44:48], requestDestination.Addr().AsSlice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binary.NativeEndian.PutUint32(request[60:64], 0)
|
|
||||||
binary.NativeEndian.PutUint64(request[64:72], 0xFFFFFFFFFFFFFFFF)
|
|
||||||
return request
|
|
||||||
}
|
|
||||||
|
|
||||||
func querySocketDiag(fd int, request []byte) (inode, uid uint32, err error) {
|
|
||||||
_, err = syscall.Write(fd, request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, E.Cause(err, "write netlink request")
|
return 0, 0, E.Cause(err, "write netlink request")
|
||||||
}
|
}
|
||||||
buffer := make([]byte, 64<<10)
|
|
||||||
n, err := syscall.Read(fd, buffer)
|
buffer := buf.New()
|
||||||
|
defer buffer.Release()
|
||||||
|
|
||||||
|
n, err := syscall.Read(socket, buffer.FreeBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, E.Cause(err, "read netlink response")
|
return 0, 0, E.Cause(err, "read netlink response")
|
||||||
}
|
}
|
||||||
messages, err := syscall.ParseNetlinkMessage(buffer[:n])
|
|
||||||
|
buffer.Truncate(n)
|
||||||
|
|
||||||
|
messages, err := syscall.ParseNetlinkMessage(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, E.Cause(err, "parse netlink message")
|
return 0, 0, E.Cause(err, "parse netlink message")
|
||||||
|
} else if len(messages) == 0 {
|
||||||
|
return 0, 0, E.New("unexcepted netlink response")
|
||||||
}
|
}
|
||||||
return unpackSocketDiagMessages(messages)
|
|
||||||
|
message := messages[0]
|
||||||
|
if message.Header.Type&syscall.NLMSG_ERROR != 0 {
|
||||||
|
return 0, 0, E.New("netlink message: NLMSG_ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
inode, uid = unpackSocketDiagResponse(&messages[0])
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func unpackSocketDiagMessages(messages []syscall.NetlinkMessage) (inode, uid uint32, err error) {
|
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {
|
||||||
for _, message := range messages {
|
s := make([]byte, 16)
|
||||||
switch message.Header.Type {
|
copy(s, source.Addr().AsSlice())
|
||||||
case syscall.NLMSG_DONE:
|
|
||||||
continue
|
buf := make([]byte, sizeOfSocketDiagRequest)
|
||||||
case syscall.NLMSG_ERROR:
|
|
||||||
err = unpackSocketDiagError(&message)
|
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
|
||||||
if err != nil {
|
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
|
||||||
return 0, 0, err
|
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
|
||||||
}
|
nativeEndian.PutUint32(buf[8:12], 0)
|
||||||
case socketDiagByFamily:
|
nativeEndian.PutUint32(buf[12:16], 0)
|
||||||
inode, uid = unpackSocketDiagResponse(&message)
|
|
||||||
if inode != 0 || uid != 0 {
|
buf[16] = family
|
||||||
return inode, uid, nil
|
buf[17] = protocol
|
||||||
}
|
buf[18] = 0
|
||||||
}
|
buf[19] = 0
|
||||||
}
|
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
|
||||||
return 0, 0, ErrNotFound
|
|
||||||
|
binary.BigEndian.PutUint16(buf[24:26], source.Port())
|
||||||
|
binary.BigEndian.PutUint16(buf[26:28], 0)
|
||||||
|
|
||||||
|
copy(buf[28:44], s)
|
||||||
|
copy(buf[44:60], net.IPv6zero)
|
||||||
|
|
||||||
|
nativeEndian.PutUint32(buf[60:64], 0)
|
||||||
|
nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF)
|
||||||
|
|
||||||
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
|
func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) {
|
||||||
if len(msg.Data) < socketDiagResponseMinSize {
|
if len(msg.Data) < 72 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
uid = binary.NativeEndian.Uint32(msg.Data[64:68])
|
|
||||||
inode = binary.NativeEndian.Uint32(msg.Data[68:72])
|
data := msg.Data
|
||||||
return inode, uid
|
|
||||||
|
uid = nativeEndian.Uint32(data[64:68])
|
||||||
|
inode = nativeEndian.Uint32(data[68:72])
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func unpackSocketDiagError(msg *syscall.NetlinkMessage) error {
|
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
|
||||||
if len(msg.Data) < 4 {
|
|
||||||
return E.New("netlink message: NLMSG_ERROR")
|
|
||||||
}
|
|
||||||
errno := int32(binary.NativeEndian.Uint32(msg.Data[:4]))
|
|
||||||
if errno == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if errno < 0 {
|
|
||||||
errno = -errno
|
|
||||||
}
|
|
||||||
sysErr := syscall.Errno(errno)
|
|
||||||
switch sysErr {
|
|
||||||
case syscall.ENOENT, syscall.ESRCH:
|
|
||||||
return ErrNotFound
|
|
||||||
default:
|
|
||||||
return E.New("netlink message: ", sysErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldRetrySocketDiag(err error) bool {
|
|
||||||
return err != nil && !errors.Is(err, ErrNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildProcessPathByUIDCache(uid uint32) (map[uint32]string, error) {
|
|
||||||
files, err := os.ReadDir(pathProc)
|
files, err := os.ReadDir(pathProc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := make([]byte, syscall.PathMax)
|
buffer := make([]byte, syscall.PathMax)
|
||||||
processPaths := make(map[uint32]string)
|
socket := []byte(fmt.Sprintf("socket:[%d]", inode))
|
||||||
for _, file := range files {
|
|
||||||
if !file.IsDir() || !isPid(file.Name()) {
|
for _, f := range files {
|
||||||
|
if !f.IsDir() || !isPid(f.Name()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info, err := file.Info()
|
|
||||||
|
info, err := f.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isIgnorableProcError(err) {
|
return "", err
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
if info.Sys().(*syscall.Stat_t).Uid != uid {
|
if info.Sys().(*syscall.Stat_t).Uid != uid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
processPath := filepath.Join(pathProc, file.Name())
|
|
||||||
fdPath := filepath.Join(processPath, "fd")
|
processPath := path.Join(pathProc, f.Name())
|
||||||
exePath, err := os.Readlink(filepath.Join(processPath, "exe"))
|
fdPath := path.Join(processPath, "fd")
|
||||||
if err != nil {
|
|
||||||
if isIgnorableProcError(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fds, err := os.ReadDir(fdPath)
|
fds, err := os.ReadDir(fdPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fd := range fds {
|
for _, fd := range fds {
|
||||||
n, err := syscall.Readlink(filepath.Join(fdPath, fd.Name()), buffer)
|
n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
inode, ok := parseSocketInode(buffer[:n])
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, loaded := processPaths[inode]; !loaded {
|
|
||||||
processPaths[inode] = exePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return processPaths, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isIgnorableProcError(err error) bool {
|
if bytes.Equal(buffer[:n], socket) {
|
||||||
return os.IsNotExist(err) || os.IsPermission(err)
|
return os.Readlink(path.Join(processPath, "exe"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseSocketInode(link []byte) (uint32, bool) {
|
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
|
||||||
const socketPrefix = "socket:["
|
|
||||||
if len(link) <= len(socketPrefix) || string(link[:len(socketPrefix)]) != socketPrefix || link[len(link)-1] != ']' {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
var inode uint64
|
|
||||||
for _, char := range link[len(socketPrefix) : len(link)-1] {
|
|
||||||
if char < '0' || char > '9' {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
inode = inode*10 + uint64(char-'0')
|
|
||||||
if inode > uint64(^uint32(0)) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uint32(inode), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPid(s string) bool {
|
func isPid(s string) bool {
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package process
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestQuerySocketDiagUDPExact(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
server, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
client, err := net.DialUDP("udp4", nil, server.LocalAddr().(*net.UDPAddr))
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
err = client.SetDeadline(time.Now().Add(time.Second))
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = client.Write([]byte{0})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = server.SetReadDeadline(time.Now().Add(time.Second))
|
|
||||||
require.NoError(t, err)
|
|
||||||
buffer := make([]byte, 1)
|
|
||||||
_, _, err = server.ReadFromUDP(buffer)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
source := addrPortFromUDPAddr(t, client.LocalAddr())
|
|
||||||
destination := addrPortFromUDPAddr(t, client.RemoteAddr())
|
|
||||||
|
|
||||||
fd, err := openSocketDiag()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer syscall.Close(fd)
|
|
||||||
|
|
||||||
inode, uid, err := querySocketDiag(fd, packSocketDiagRequest(syscall.AF_INET, syscall.IPPROTO_UDP, source, destination, false))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotZero(t, inode)
|
|
||||||
require.EqualValues(t, os.Getuid(), uid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addrPortFromUDPAddr(t *testing.T, addr net.Addr) netip.AddrPort {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
udpAddr, ok := addr.(*net.UDPAddr)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
ip, ok := netip.AddrFromSlice(udpAddr.IP)
|
|
||||||
require.True(t, ok)
|
|
||||||
|
|
||||||
return netip.AddrPortFrom(ip.Unmap(), uint16(udpAddr.Port))
|
|
||||||
}
|
|
||||||
@@ -28,10 +28,6 @@ func initWin32API() error {
|
|||||||
return winiphlpapi.LoadExtendedTable()
|
return winiphlpapi.LoadExtendedTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *windowsSearcher) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
|
||||||
pid, err := winiphlpapi.FindPid(network, source)
|
pid, err := winiphlpapi.FindPid(network, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -303,6 +303,8 @@ find:
|
|||||||
metadata.Protocol = C.ProtocolQUIC
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
fingerprint, err := ja3.Compute(buffer.Bytes())
|
fingerprint, err := ja3.Compute(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
|
metadata.Client = C.ClientChromium
|
||||||
metadata.SniffContext = fragments
|
metadata.SniffContext = fragments
|
||||||
return E.Cause1(ErrNeedMoreData, err)
|
return E.Cause1(ErrNeedMoreData, err)
|
||||||
}
|
}
|
||||||
@@ -332,7 +334,7 @@ find:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 {
|
if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 {
|
||||||
if isQUICGo(fingerprint) {
|
if maybeUQUIC(fingerprint) {
|
||||||
metadata.Client = C.ClientQUICGo
|
metadata.Client = C.ClientQUICGo
|
||||||
} else {
|
} else {
|
||||||
metadata.Client = C.ClientChromium
|
metadata.Client = C.ClientChromium
|
||||||
|
|||||||
@@ -1,29 +1,21 @@
|
|||||||
package sniff
|
package sniff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/ja3"
|
"github.com/sagernet/sing-box/common/ja3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// Chromium sends separate client hello packets, but UQUIC has not yet implemented this behavior
|
||||||
// X25519Kyber768Draft00 - post-quantum curve used by Go crypto/tls
|
// The cronet without this behavior does not have version 115
|
||||||
x25519Kyber768Draft00 uint16 = 0x11EC // 4588
|
var uQUICChrome115 = &ja3.ClientHello{
|
||||||
// renegotiation_info extension used by Go crypto/tls
|
Version: tls.VersionTLS12,
|
||||||
extensionRenegotiationInfo uint16 = 0xFF01 // 65281
|
CipherSuites: []uint16{4865, 4866, 4867},
|
||||||
)
|
Extensions: []uint16{0, 10, 13, 16, 27, 43, 45, 51, 57, 17513},
|
||||||
|
EllipticCurves: []uint16{29, 23, 24},
|
||||||
// isQUICGo detects native quic-go by checking for Go crypto/tls specific features.
|
SignatureAlgorithms: []uint16{1027, 2052, 1025, 1283, 2053, 1281, 2054, 1537, 513},
|
||||||
// Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium
|
}
|
||||||
// since it uses the same TLS fingerprint, so it will be identified as Chromium.
|
|
||||||
func isQUICGo(fingerprint *ja3.ClientHello) bool {
|
func maybeUQUIC(fingerprint *ja3.ClientHello) bool {
|
||||||
for _, curve := range fingerprint.EllipticCurves {
|
return !uQUICChrome115.Equals(fingerprint, true)
|
||||||
if curve == x25519Kyber768Draft00 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ext := range fingerprint.Extensions {
|
|
||||||
if ext == extensionRenegotiationInfo {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
package sniff_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSniffQUICQuicGoFingerprint(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const testSNI = "test.example.com"
|
|
||||||
|
|
||||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer udpConn.Close()
|
|
||||||
|
|
||||||
serverAddr := udpConn.LocalAddr().(*net.UDPAddr)
|
|
||||||
packetsChan := make(chan [][]byte, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
var packets [][]byte
|
|
||||||
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
buf := make([]byte, 2048)
|
|
||||||
n, _, err := udpConn.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
packets = append(packets, buf[:n])
|
|
||||||
}
|
|
||||||
packetsChan <- packets
|
|
||||||
}()
|
|
||||||
|
|
||||||
clientConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer clientConn.Close()
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: testSNI,
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
NextProtos: []string{"h3"},
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
_, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{})
|
|
||||||
|
|
||||||
select {
|
|
||||||
case packets := <-packetsChan:
|
|
||||||
t.Logf("Captured %d packets", len(packets))
|
|
||||||
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
for i, pkt := range packets {
|
|
||||||
err := sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
|
||||||
t.Logf("Packet %d: err=%v, domain=%s, client=%s", i, err, metadata.Domain, metadata.Client)
|
|
||||||
if metadata.Domain != "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("\n=== quic-go TLS Fingerprint Analysis ===")
|
|
||||||
t.Logf("Domain: %s", metadata.Domain)
|
|
||||||
t.Logf("Client: %s", metadata.Client)
|
|
||||||
t.Logf("Protocol: %s", metadata.Protocol)
|
|
||||||
|
|
||||||
// The client should be identified as quic-go, not chromium
|
|
||||||
// Current issue: it's being identified as chromium
|
|
||||||
if metadata.Client == "chromium" {
|
|
||||||
t.Log("WARNING: quic-go is being misidentified as chromium!")
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Timeout")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffQUICInitialFromQuicGo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const testSNI = "test.example.com"
|
|
||||||
|
|
||||||
// Create UDP listener to capture ALL initial packets
|
|
||||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer udpConn.Close()
|
|
||||||
|
|
||||||
serverAddr := udpConn.LocalAddr().(*net.UDPAddr)
|
|
||||||
|
|
||||||
// Channel to receive captured packets
|
|
||||||
packetsChan := make(chan [][]byte, 1)
|
|
||||||
|
|
||||||
// Start goroutine to capture packets
|
|
||||||
go func() {
|
|
||||||
var packets [][]byte
|
|
||||||
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
||||||
for i := 0; i < 5; i++ { // Capture up to 5 packets
|
|
||||||
buf := make([]byte, 2048)
|
|
||||||
n, _, err := udpConn.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
packets = append(packets, buf[:n])
|
|
||||||
}
|
|
||||||
packetsChan <- packets
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Create QUIC client connection (will fail but we capture the initial packet)
|
|
||||||
clientConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer clientConn.Close()
|
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
ServerName: testSNI,
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
NextProtos: []string{"h3"},
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// This will fail (no server) but sends initial packet
|
|
||||||
_, _ = quic.Dial(ctx, clientConn, serverAddr, tlsConfig, &quic.Config{})
|
|
||||||
|
|
||||||
// Wait for captured packets
|
|
||||||
select {
|
|
||||||
case packets := <-packetsChan:
|
|
||||||
t.Logf("Captured %d QUIC packets", len(packets))
|
|
||||||
|
|
||||||
for i, packet := range packets {
|
|
||||||
t.Logf("Packet %d: length=%d, first 30 bytes: %x", i, len(packet), packet[:min(30, len(packet))])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test sniffer with first packet
|
|
||||||
if len(packets) > 0 {
|
|
||||||
var metadata adapter.InboundContext
|
|
||||||
err := sniff.QUICClientHello(context.Background(), &metadata, packets[0])
|
|
||||||
|
|
||||||
t.Logf("First packet sniff error: %v", err)
|
|
||||||
t.Logf("Protocol: %s", metadata.Protocol)
|
|
||||||
t.Logf("Domain: %s", metadata.Domain)
|
|
||||||
t.Logf("Client: %s", metadata.Client)
|
|
||||||
|
|
||||||
// If first packet needs more data, try with subsequent packets
|
|
||||||
// IMPORTANT: reuse metadata to accumulate CRYPTO fragments via SniffContext
|
|
||||||
if errors.Is(err, sniff.ErrNeedMoreData) && len(packets) > 1 {
|
|
||||||
t.Log("First packet needs more data, trying subsequent packets with shared context...")
|
|
||||||
for i := 1; i < len(packets); i++ {
|
|
||||||
// Reuse same metadata to accumulate fragments
|
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, packets[i])
|
|
||||||
t.Logf("Packet %d sniff result: err=%v, domain=%s, sniffCtx=%v", i, err, metadata.Domain, metadata.SniffContext != nil)
|
|
||||||
if metadata.Domain != "" || (err != nil && !errors.Is(err, sniff.ErrNeedMoreData)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print hex dump for debugging
|
|
||||||
t.Logf("First packet hex:\n%s", hex.Dump(packets[0][:min(256, len(packets[0]))]))
|
|
||||||
|
|
||||||
// Log final results
|
|
||||||
t.Logf("Final: Protocol=%s, Domain=%s, Client=%s", metadata.Protocol, metadata.Domain, metadata.Client)
|
|
||||||
|
|
||||||
// Verify SNI extraction
|
|
||||||
if metadata.Domain == "" {
|
|
||||||
t.Errorf("Failed to extract SNI, expected: %s", testSNI)
|
|
||||||
} else {
|
|
||||||
require.Equal(t, testSNI, metadata.Domain, "SNI should match")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check client identification - quic-go should be identified as quic-go, not chromium
|
|
||||||
t.Logf("Client identified as: %s (expected: quic-go)", metadata.Client)
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-time.After(5 * time.Second):
|
|
||||||
t.Fatal("Timeout waiting for QUIC packets")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ func TestSniffQUICChromeNew(t *testing.T) {
|
|||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Empty(t, metadata.Client)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -39,7 +39,7 @@ func TestSniffQUICChromium(t *testing.T) {
|
|||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
|
||||||
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
|
||||||
require.Empty(t, metadata.Client)
|
require.Equal(t, metadata.Client, C.ClientChromium)
|
||||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||||
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -46,7 +45,6 @@ const (
|
|||||||
ruleItemNetworkIsConstrained
|
ruleItemNetworkIsConstrained
|
||||||
ruleItemNetworkInterfaceAddress
|
ruleItemNetworkInterfaceAddress
|
||||||
ruleItemDefaultInterfaceAddress
|
ruleItemDefaultInterfaceAddress
|
||||||
ruleItemPackageNameRegex
|
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -216,8 +214,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageName:
|
case ruleItemPackageName:
|
||||||
rule.PackageName, err = readRuleItemString(reader)
|
rule.PackageName, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageNameRegex:
|
|
||||||
rule.PackageNameRegex, err = readRuleItemString(reader)
|
|
||||||
case ruleItemWIFISSID:
|
case ruleItemWIFISSID:
|
||||||
rule.WIFISSID, err = readRuleItemString(reader)
|
rule.WIFISSID, err = readRuleItemString(reader)
|
||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
@@ -397,15 +393,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rule.PackageNameRegex) > 0 {
|
|
||||||
if generateVersion < C.RuleSetVersion5 {
|
|
||||||
return E.New("`package_name_regex` rule item is only supported in version 5 or later")
|
|
||||||
}
|
|
||||||
err = writeRuleItemString(writer, ruleItemPackageNameRegex, rule.PackageNameRegex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(rule.NetworkType) > 0 {
|
if len(rule.NetworkType) > 0 {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
return E.New("`network_type` rule item is only supported in version 3 or later")
|
return E.New("`network_type` rule item is only supported in version 3 or later")
|
||||||
@@ -518,24 +505,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readRuleItemString(reader varbin.Reader) ([]string, error) {
|
func readRuleItemString(reader varbin.Reader) ([]string, error) {
|
||||||
length, err := binary.ReadUvarint(reader)
|
return varbin.ReadValue[[]string](reader, binary.BigEndian)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]string, length)
|
|
||||||
for i := range result {
|
|
||||||
strLen, err := binary.ReadUvarint(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
buf := make([]byte, strLen)
|
|
||||||
_, err = io.ReadFull(reader, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result[i] = string(buf)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
|
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
|
||||||
@@ -543,34 +513,11 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, s := range value {
|
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(s)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write([]byte(s))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
||||||
length, err := binary.ReadUvarint(reader)
|
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]E, length)
|
|
||||||
_, err = io.ReadFull(reader, *(*[]byte)(unsafe.Pointer(&result)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
||||||
@@ -578,25 +525,11 @@ func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||||
length, err := binary.ReadUvarint(reader)
|
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result := make([]uint16, length)
|
|
||||||
err = binary.Read(reader, binary.BigEndian, result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
|
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
|
||||||
@@ -604,11 +537,7 @@ func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return binary.Write(writer, binary.BigEndian, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
|
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
|
||||||
|
|||||||
@@ -1,494 +0,0 @@
|
|||||||
package srs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"go4.org/netipx"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Old implementations using varbin reflection-based serialization
|
|
||||||
|
|
||||||
func oldWriteStringSlice(writer varbin.Writer, value []string) error {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldReadStringSlice(reader varbin.Reader) ([]string, error) {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.ReadValue[[]string](reader, binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldReadUint8Slice[E ~uint8](reader varbin.Reader) ([]E, error) {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldWriteUint16Slice(writer varbin.Writer, value []uint16) error {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.Write(writer, binary.BigEndian, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldReadUint16Slice(reader varbin.Reader) ([]uint16, error) {
|
|
||||||
//nolint:staticcheck
|
|
||||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldWritePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
|
||||||
//nolint:staticcheck
|
|
||||||
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
|
||||||
}
|
|
||||||
|
|
||||||
type oldIPRangeData struct {
|
|
||||||
From []byte
|
|
||||||
To []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: The old writeIPSet had a bug where varbin.Write(writer, binary.BigEndian, data)
|
|
||||||
// with a struct VALUE (not pointer) silently wrote nothing because field.CanSet() returned false.
|
|
||||||
// This caused IP range data to be missing from the output.
|
|
||||||
// The new implementation correctly writes all range data.
|
|
||||||
//
|
|
||||||
// The old readIPSet used varbin.Read with a pre-allocated slice, which worked because
|
|
||||||
// slice elements are addressable and CanSet() returns true for them.
|
|
||||||
//
|
|
||||||
// For compatibility testing, we verify:
|
|
||||||
// 1. New write produces correct output with range data
|
|
||||||
// 2. New read can parse the new format correctly
|
|
||||||
// 3. Round-trip works correctly
|
|
||||||
|
|
||||||
func oldReadIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
|
||||||
version, err := reader.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if version != 1 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var length uint64
|
|
||||||
err = binary.Read(reader, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ranges := make([]oldIPRangeData, length)
|
|
||||||
//nolint:staticcheck
|
|
||||||
err = varbin.Read(reader, binary.BigEndian, &ranges)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mySet := &myIPSet{
|
|
||||||
rr: make([]myIPRange, len(ranges)),
|
|
||||||
}
|
|
||||||
for i, rangeData := range ranges {
|
|
||||||
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
|
|
||||||
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
|
|
||||||
}
|
|
||||||
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New write functions (without itemType prefix for testing)
|
|
||||||
|
|
||||||
func newWriteStringSlice(writer varbin.Writer, value []string) error {
|
|
||||||
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, s := range value {
|
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(s)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write([]byte(s))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWriteUint8Slice[E ~uint8](writer varbin.Writer, value []E) error {
|
|
||||||
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWriteUint16Slice(writer varbin.Writer, value []uint16) error {
|
|
||||||
_, err := varbin.WriteUvarint(writer, uint64(len(value)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return binary.Write(writer, binary.BigEndian, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWritePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
|
||||||
addrSlice := prefix.Addr().AsSlice()
|
|
||||||
_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write(addrSlice)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return writer.WriteByte(uint8(prefix.Bits()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests
|
|
||||||
|
|
||||||
func TestStringSliceCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input []string
|
|
||||||
}{
|
|
||||||
{"nil", nil},
|
|
||||||
{"empty", []string{}},
|
|
||||||
{"single_empty", []string{""}},
|
|
||||||
{"single", []string{"test"}},
|
|
||||||
{"multi", []string{"a", "b", "c"}},
|
|
||||||
{"with_empty", []string{"a", "", "c"}},
|
|
||||||
{"utf8", []string{"测试", "テスト", "тест"}},
|
|
||||||
{"long_string", []string{strings.Repeat("x", 128)}},
|
|
||||||
{"many_elements", generateStrings(128)},
|
|
||||||
{"many_elements_256", generateStrings(256)},
|
|
||||||
{"127_byte_string", []string{strings.Repeat("x", 127)}},
|
|
||||||
{"128_byte_string", []string{strings.Repeat("x", 128)}},
|
|
||||||
{"mixed_lengths", []string{"a", strings.Repeat("b", 100), "", strings.Repeat("c", 200)}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Old write
|
|
||||||
var oldBuf bytes.Buffer
|
|
||||||
err := oldWriteStringSlice(&oldBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err = newWriteStringSlice(&newBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Bytes must match
|
|
||||||
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
|
||||||
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
|
||||||
|
|
||||||
// New write -> old read
|
|
||||||
readBack, err := oldReadStringSlice(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireStringSliceEqual(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// Old write -> new read
|
|
||||||
readBack2, err := readRuleItemString(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireStringSliceEqual(t, tc.input, readBack2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUint8SliceCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input []uint8
|
|
||||||
}{
|
|
||||||
{"nil", nil},
|
|
||||||
{"empty", []uint8{}},
|
|
||||||
{"single_zero", []uint8{0}},
|
|
||||||
{"single_max", []uint8{255}},
|
|
||||||
{"multi", []uint8{0, 1, 127, 128, 255}},
|
|
||||||
{"boundary", []uint8{0x00, 0x7f, 0x80, 0xff}},
|
|
||||||
{"sequential", generateUint8Slice(256)},
|
|
||||||
{"127_elements", generateUint8Slice(127)},
|
|
||||||
{"128_elements", generateUint8Slice(128)},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Old write
|
|
||||||
var oldBuf bytes.Buffer
|
|
||||||
err := oldWriteUint8Slice(&oldBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err = newWriteUint8Slice(&newBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Bytes must match
|
|
||||||
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
|
||||||
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
|
||||||
|
|
||||||
// New write -> old read
|
|
||||||
readBack, err := oldReadUint8Slice[uint8](bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireUint8SliceEqual(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// Old write -> new read
|
|
||||||
readBack2, err := readRuleItemUint8[uint8](bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireUint8SliceEqual(t, tc.input, readBack2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUint16SliceCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input []uint16
|
|
||||||
}{
|
|
||||||
{"nil", nil},
|
|
||||||
{"empty", []uint16{}},
|
|
||||||
{"single_zero", []uint16{0}},
|
|
||||||
{"single_max", []uint16{65535}},
|
|
||||||
{"multi", []uint16{0, 255, 256, 32767, 32768, 65535}},
|
|
||||||
{"ports", []uint16{80, 443, 8080, 8443}},
|
|
||||||
{"127_elements", generateUint16Slice(127)},
|
|
||||||
{"128_elements", generateUint16Slice(128)},
|
|
||||||
{"256_elements", generateUint16Slice(256)},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Old write
|
|
||||||
var oldBuf bytes.Buffer
|
|
||||||
err := oldWriteUint16Slice(&oldBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err = newWriteUint16Slice(&newBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Bytes must match
|
|
||||||
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
|
||||||
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
|
||||||
|
|
||||||
// New write -> old read
|
|
||||||
readBack, err := oldReadUint16Slice(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireUint16SliceEqual(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// Old write -> new read
|
|
||||||
readBack2, err := readRuleItemUint16(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireUint16SliceEqual(t, tc.input, readBack2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrefixCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input netip.Prefix
|
|
||||||
}{
|
|
||||||
{"ipv4_0", netip.MustParsePrefix("0.0.0.0/0")},
|
|
||||||
{"ipv4_8", netip.MustParsePrefix("10.0.0.0/8")},
|
|
||||||
{"ipv4_16", netip.MustParsePrefix("192.168.0.0/16")},
|
|
||||||
{"ipv4_24", netip.MustParsePrefix("192.168.1.0/24")},
|
|
||||||
{"ipv4_32", netip.MustParsePrefix("1.2.3.4/32")},
|
|
||||||
{"ipv6_0", netip.MustParsePrefix("::/0")},
|
|
||||||
{"ipv6_64", netip.MustParsePrefix("2001:db8::/64")},
|
|
||||||
{"ipv6_128", netip.MustParsePrefix("::1/128")},
|
|
||||||
{"ipv6_full", netip.MustParsePrefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334/128")},
|
|
||||||
{"ipv4_private", netip.MustParsePrefix("172.16.0.0/12")},
|
|
||||||
{"ipv6_link_local", netip.MustParsePrefix("fe80::/10")},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Old write
|
|
||||||
var oldBuf bytes.Buffer
|
|
||||||
err := oldWritePrefix(&oldBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err = newWritePrefix(&newBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Bytes must match
|
|
||||||
require.Equal(t, oldBuf.Bytes(), newBuf.Bytes(),
|
|
||||||
"mismatch for %q\nold: %x\nnew: %x", tc.name, oldBuf.Bytes(), newBuf.Bytes())
|
|
||||||
|
|
||||||
// New write -> new read (no old read for prefix)
|
|
||||||
readBack, err := readPrefix(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// Old write -> new read
|
|
||||||
readBack2, err := readPrefix(bufio.NewReader(bytes.NewReader(oldBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tc.input, readBack2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIPSetCompat(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Note: The old writeIPSet was buggy (varbin.Write with struct values wrote nothing).
|
|
||||||
// This test verifies the new implementation writes correct data and round-trips correctly.
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
input *netipx.IPSet
|
|
||||||
}{
|
|
||||||
{"single_ipv4", buildIPSet("1.2.3.4")},
|
|
||||||
{"ipv4_range", buildIPSet("192.168.0.0/16")},
|
|
||||||
{"multi_ipv4", buildIPSet("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")},
|
|
||||||
{"single_ipv6", buildIPSet("::1")},
|
|
||||||
{"ipv6_range", buildIPSet("2001:db8::/32")},
|
|
||||||
{"mixed", buildIPSet("10.0.0.0/8", "::1", "2001:db8::/32")},
|
|
||||||
{"large", buildLargeIPSet(100)},
|
|
||||||
{"adjacent_ranges", buildIPSet("192.168.0.0/24", "192.168.1.0/24", "192.168.2.0/24")},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// New write
|
|
||||||
var newBuf bytes.Buffer
|
|
||||||
err := writeIPSet(&newBuf, tc.input)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify format starts with version byte (1) + uint64 count
|
|
||||||
require.True(t, len(newBuf.Bytes()) >= 9, "output too short")
|
|
||||||
require.Equal(t, byte(1), newBuf.Bytes()[0], "version byte mismatch")
|
|
||||||
|
|
||||||
// New write -> old read (varbin.Read with pre-allocated slice works correctly)
|
|
||||||
readBack, err := oldReadIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireIPSetEqual(t, tc.input, readBack)
|
|
||||||
|
|
||||||
// New write -> new read
|
|
||||||
readBack2, err := readIPSet(bufio.NewReader(bytes.NewReader(newBuf.Bytes())))
|
|
||||||
require.NoError(t, err)
|
|
||||||
requireIPSetEqual(t, tc.input, readBack2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
|
|
||||||
func generateStrings(count int) []string {
|
|
||||||
result := make([]string, count)
|
|
||||||
for i := range result {
|
|
||||||
result[i] = strings.Repeat("x", i%50)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateUint8Slice(count int) []uint8 {
|
|
||||||
result := make([]uint8, count)
|
|
||||||
for i := range result {
|
|
||||||
result[i] = uint8(i % 256)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateUint16Slice(count int) []uint16 {
|
|
||||||
result := make([]uint16, count)
|
|
||||||
for i := range result {
|
|
||||||
result[i] = uint16(i * 257)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildIPSet(cidrs ...string) *netipx.IPSet {
|
|
||||||
var builder netipx.IPSetBuilder
|
|
||||||
for _, cidr := range cidrs {
|
|
||||||
prefix, err := netip.ParsePrefix(cidr)
|
|
||||||
if err != nil {
|
|
||||||
addr, err := netip.ParseAddr(cidr)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
builder.Add(addr)
|
|
||||||
} else {
|
|
||||||
builder.AddPrefix(prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set, _ := builder.IPSet()
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildLargeIPSet(count int) *netipx.IPSet {
|
|
||||||
var builder netipx.IPSetBuilder
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
prefix := netip.PrefixFrom(netip.AddrFrom4([4]byte{10, byte(i / 256), byte(i % 256), 0}), 24)
|
|
||||||
builder.AddPrefix(prefix)
|
|
||||||
}
|
|
||||||
set, _ := builder.IPSet()
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireStringSliceEqual(t *testing.T, expected, actual []string) {
|
|
||||||
t.Helper()
|
|
||||||
if len(expected) == 0 && len(actual) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireUint8SliceEqual(t *testing.T, expected, actual []uint8) {
|
|
||||||
t.Helper()
|
|
||||||
if len(expected) == 0 && len(actual) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireUint16SliceEqual(t *testing.T, expected, actual []uint16) {
|
|
||||||
t.Helper()
|
|
||||||
if len(expected) == 0 && len(actual) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
func requireIPSetEqual(t *testing.T, expected, actual *netipx.IPSet) {
|
|
||||||
t.Helper()
|
|
||||||
expectedRanges := expected.Ranges()
|
|
||||||
actualRanges := actual.Ranges()
|
|
||||||
require.Equal(t, len(expectedRanges), len(actualRanges), "range count mismatch")
|
|
||||||
for i := range expectedRanges {
|
|
||||||
require.Equal(t, expectedRanges[i].From(), actualRanges[i].From(), "range[%d].from mismatch", i)
|
|
||||||
require.Equal(t, expectedRanges[i].To(), actualRanges[i].To(), "range[%d].to mismatch", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package srs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -10,16 +9,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
||||||
addrLen, err := binary.ReadUvarint(reader)
|
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Prefix{}, err
|
return netip.Prefix{}, err
|
||||||
}
|
}
|
||||||
addrSlice := make([]byte, addrLen)
|
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
|
||||||
_, err = io.ReadFull(reader, addrSlice)
|
|
||||||
if err != nil {
|
|
||||||
return netip.Prefix{}, err
|
|
||||||
}
|
|
||||||
prefixBits, err := reader.ReadByte()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Prefix{}, err
|
return netip.Prefix{}, err
|
||||||
}
|
}
|
||||||
@@ -27,16 +21,11 @@ func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
||||||
addrSlice := prefix.Addr().AsSlice()
|
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
||||||
_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = writer.Write(addrSlice)
|
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = writer.WriteByte(uint8(prefix.Bits()))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package srs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
@@ -22,6 +22,11 @@ type myIPRange struct {
|
|||||||
to netip.Addr
|
to netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type myIPRangeData struct {
|
||||||
|
From []byte
|
||||||
|
To []byte
|
||||||
|
}
|
||||||
|
|
||||||
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
||||||
version, err := reader.ReadByte()
|
version, err := reader.ReadByte()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -36,30 +41,17 @@ func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ranges := make([]myIPRangeData, length)
|
||||||
|
err = varbin.Read(reader, binary.BigEndian, &ranges)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
mySet := &myIPSet{
|
mySet := &myIPSet{
|
||||||
rr: make([]myIPRange, length),
|
rr: make([]myIPRange, len(ranges)),
|
||||||
}
|
}
|
||||||
for i := range mySet.rr {
|
for i, rangeData := range ranges {
|
||||||
fromLen, err := binary.ReadUvarint(reader)
|
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
|
||||||
if err != nil {
|
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fromBytes := make([]byte, fromLen)
|
|
||||||
_, err = io.ReadFull(reader, fromBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
toLen, err := binary.ReadUvarint(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
toBytes := make([]byte, toLen)
|
|
||||||
_, err = io.ReadFull(reader, toBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mySet.rr[i].from = M.AddrFromIP(fromBytes)
|
|
||||||
mySet.rr[i].to = M.AddrFromIP(toBytes)
|
|
||||||
}
|
}
|
||||||
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
||||||
}
|
}
|
||||||
@@ -69,27 +61,18 @@ func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mySet := (*myIPSet)(unsafe.Pointer(set))
|
dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData {
|
||||||
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
|
return myIPRangeData{
|
||||||
|
From: rr.from.AsSlice(),
|
||||||
|
To: rr.to.AsSlice(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint64(len(dataList)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rr := range mySet.rr {
|
for _, data := range dataList {
|
||||||
fromBytes := rr.from.AsSlice()
|
err = varbin.Write(writer, binary.BigEndian, data)
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(fromBytes)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write(fromBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
toBytes := rr.to.AsSlice()
|
|
||||||
_, err = varbin.WriteUvarint(writer, uint64(len(toBytes)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = writer.Write(toBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,612 +0,0 @@
|
|||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/bufio/deadline"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultServer = "stun.voipgate.com:3478"
|
|
||||||
|
|
||||||
magicCookie = 0x2112A442
|
|
||||||
headerSize = 20
|
|
||||||
|
|
||||||
bindingRequest = 0x0001
|
|
||||||
bindingSuccessResponse = 0x0101
|
|
||||||
bindingErrorResponse = 0x0111
|
|
||||||
|
|
||||||
attrMappedAddress = 0x0001
|
|
||||||
attrChangeRequest = 0x0003
|
|
||||||
attrErrorCode = 0x0009
|
|
||||||
attrXORMappedAddress = 0x0020
|
|
||||||
attrOtherAddress = 0x802c
|
|
||||||
|
|
||||||
familyIPv4 = 0x01
|
|
||||||
familyIPv6 = 0x02
|
|
||||||
|
|
||||||
changeIP = 0x04
|
|
||||||
changePort = 0x02
|
|
||||||
|
|
||||||
defaultRTO = 500 * time.Millisecond
|
|
||||||
minRTO = 250 * time.Millisecond
|
|
||||||
maxRetransmit = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Phase int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
PhaseBinding Phase = iota
|
|
||||||
PhaseNATMapping
|
|
||||||
PhaseNATFiltering
|
|
||||||
PhaseDone
|
|
||||||
)
|
|
||||||
|
|
||||||
type NATMapping int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
NATMappingUnknown NATMapping = iota
|
|
||||||
_ // reserved
|
|
||||||
NATMappingEndpointIndependent
|
|
||||||
NATMappingAddressDependent
|
|
||||||
NATMappingAddressAndPortDependent
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m NATMapping) String() string {
|
|
||||||
switch m {
|
|
||||||
case NATMappingEndpointIndependent:
|
|
||||||
return "Endpoint Independent"
|
|
||||||
case NATMappingAddressDependent:
|
|
||||||
return "Address Dependent"
|
|
||||||
case NATMappingAddressAndPortDependent:
|
|
||||||
return "Address and Port Dependent"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NATFiltering int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
NATFilteringUnknown NATFiltering = iota
|
|
||||||
NATFilteringEndpointIndependent
|
|
||||||
NATFilteringAddressDependent
|
|
||||||
NATFilteringAddressAndPortDependent
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f NATFiltering) String() string {
|
|
||||||
switch f {
|
|
||||||
case NATFilteringEndpointIndependent:
|
|
||||||
return "Endpoint Independent"
|
|
||||||
case NATFilteringAddressDependent:
|
|
||||||
return "Address Dependent"
|
|
||||||
case NATFilteringAddressAndPortDependent:
|
|
||||||
return "Address and Port Dependent"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransactionID [12]byte
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Server string
|
|
||||||
Dialer N.Dialer
|
|
||||||
Context context.Context
|
|
||||||
OnProgress func(Progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Progress struct {
|
|
||||||
Phase Phase
|
|
||||||
ExternalAddr string
|
|
||||||
LatencyMs int32
|
|
||||||
NATMapping NATMapping
|
|
||||||
NATFiltering NATFiltering
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
ExternalAddr string
|
|
||||||
LatencyMs int32
|
|
||||||
NATMapping NATMapping
|
|
||||||
NATFiltering NATFiltering
|
|
||||||
NATTypeSupported bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type parsedResponse struct {
|
|
||||||
xorMappedAddr netip.AddrPort
|
|
||||||
mappedAddr netip.AddrPort
|
|
||||||
otherAddr netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *parsedResponse) externalAddr() (netip.AddrPort, bool) {
|
|
||||||
if r.xorMappedAddr.IsValid() {
|
|
||||||
return r.xorMappedAddr, true
|
|
||||||
}
|
|
||||||
if r.mappedAddr.IsValid() {
|
|
||||||
return r.mappedAddr, true
|
|
||||||
}
|
|
||||||
return netip.AddrPort{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type stunAttribute struct {
|
|
||||||
typ uint16
|
|
||||||
value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTransactionID() TransactionID {
|
|
||||||
var id TransactionID
|
|
||||||
_, _ = rand.Read(id[:])
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBindingRequest(txID TransactionID, attrs ...stunAttribute) []byte {
|
|
||||||
attrLen := 0
|
|
||||||
for _, attr := range attrs {
|
|
||||||
attrLen += 4 + len(attr.value) + paddingLen(len(attr.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, headerSize+attrLen)
|
|
||||||
binary.BigEndian.PutUint16(buf[0:2], bindingRequest)
|
|
||||||
binary.BigEndian.PutUint16(buf[2:4], uint16(attrLen))
|
|
||||||
binary.BigEndian.PutUint32(buf[4:8], magicCookie)
|
|
||||||
copy(buf[8:20], txID[:])
|
|
||||||
|
|
||||||
offset := headerSize
|
|
||||||
for _, attr := range attrs {
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:offset+2], attr.typ)
|
|
||||||
binary.BigEndian.PutUint16(buf[offset+2:offset+4], uint16(len(attr.value)))
|
|
||||||
copy(buf[offset+4:offset+4+len(attr.value)], attr.value)
|
|
||||||
offset += 4 + len(attr.value) + paddingLen(len(attr.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeRequestAttr(flags byte) stunAttribute {
|
|
||||||
return stunAttribute{
|
|
||||||
typ: attrChangeRequest,
|
|
||||||
value: []byte{0, 0, 0, flags},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseResponse(data []byte, expectedTxID TransactionID) (*parsedResponse, error) {
|
|
||||||
if len(data) < headerSize {
|
|
||||||
return nil, E.New("response too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
msgType := binary.BigEndian.Uint16(data[0:2])
|
|
||||||
if msgType&0xC000 != 0 {
|
|
||||||
return nil, E.New("invalid STUN message: top 2 bits not zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := binary.BigEndian.Uint32(data[4:8])
|
|
||||||
if cookie != magicCookie {
|
|
||||||
return nil, E.New("invalid magic cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
var txID TransactionID
|
|
||||||
copy(txID[:], data[8:20])
|
|
||||||
if txID != expectedTxID {
|
|
||||||
return nil, E.New("transaction ID mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
msgLen := int(binary.BigEndian.Uint16(data[2:4]))
|
|
||||||
if msgLen > len(data)-headerSize {
|
|
||||||
return nil, E.New("message length exceeds data")
|
|
||||||
}
|
|
||||||
|
|
||||||
attrData := data[headerSize : headerSize+msgLen]
|
|
||||||
|
|
||||||
if msgType == bindingErrorResponse {
|
|
||||||
return nil, parseErrorResponse(attrData)
|
|
||||||
}
|
|
||||||
if msgType != bindingSuccessResponse {
|
|
||||||
return nil, E.New("unexpected message type: ", fmt.Sprintf("0x%04x", msgType))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &parsedResponse{}
|
|
||||||
offset := 0
|
|
||||||
for offset+4 <= len(attrData) {
|
|
||||||
attrType := binary.BigEndian.Uint16(attrData[offset : offset+2])
|
|
||||||
attrLen := int(binary.BigEndian.Uint16(attrData[offset+2 : offset+4]))
|
|
||||||
if offset+4+attrLen > len(attrData) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
attrValue := attrData[offset+4 : offset+4+attrLen]
|
|
||||||
|
|
||||||
switch attrType {
|
|
||||||
case attrXORMappedAddress:
|
|
||||||
addr, err := parseXORMappedAddress(attrValue, txID)
|
|
||||||
if err == nil {
|
|
||||||
resp.xorMappedAddr = addr
|
|
||||||
}
|
|
||||||
case attrMappedAddress:
|
|
||||||
addr, err := parseMappedAddress(attrValue)
|
|
||||||
if err == nil {
|
|
||||||
resp.mappedAddr = addr
|
|
||||||
}
|
|
||||||
case attrOtherAddress:
|
|
||||||
addr, err := parseMappedAddress(attrValue)
|
|
||||||
if err == nil {
|
|
||||||
resp.otherAddr = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += 4 + attrLen + paddingLen(attrLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseErrorResponse(data []byte) error {
|
|
||||||
offset := 0
|
|
||||||
for offset+4 <= len(data) {
|
|
||||||
attrType := binary.BigEndian.Uint16(data[offset : offset+2])
|
|
||||||
attrLen := int(binary.BigEndian.Uint16(data[offset+2 : offset+4]))
|
|
||||||
if offset+4+attrLen > len(data) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if attrType == attrErrorCode && attrLen >= 4 {
|
|
||||||
attrValue := data[offset+4 : offset+4+attrLen]
|
|
||||||
class := int(attrValue[2] & 0x07)
|
|
||||||
number := int(attrValue[3])
|
|
||||||
code := class*100 + number
|
|
||||||
if attrLen > 4 {
|
|
||||||
return E.New("STUN error ", code, ": ", string(attrValue[4:]))
|
|
||||||
}
|
|
||||||
return E.New("STUN error ", code)
|
|
||||||
}
|
|
||||||
offset += 4 + attrLen + paddingLen(attrLen)
|
|
||||||
}
|
|
||||||
return E.New("STUN error response")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseXORMappedAddress(data []byte, txID TransactionID) (netip.AddrPort, error) {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := data[1]
|
|
||||||
xPort := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
port := xPort ^ uint16(magicCookie>>16)
|
|
||||||
|
|
||||||
switch family {
|
|
||||||
case familyIPv4:
|
|
||||||
if len(data) < 8 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv4 too short")
|
|
||||||
}
|
|
||||||
var ip [4]byte
|
|
||||||
binary.BigEndian.PutUint32(ip[:], binary.BigEndian.Uint32(data[4:8])^magicCookie)
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom4(ip), port), nil
|
|
||||||
case familyIPv6:
|
|
||||||
if len(data) < 20 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv6 too short")
|
|
||||||
}
|
|
||||||
var ip [16]byte
|
|
||||||
var xorKey [16]byte
|
|
||||||
binary.BigEndian.PutUint32(xorKey[0:4], magicCookie)
|
|
||||||
copy(xorKey[4:16], txID[:])
|
|
||||||
for i := range 16 {
|
|
||||||
ip[i] = data[4+i] ^ xorKey[i]
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, E.New("unknown address family: ", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMappedAddress(data []byte) (netip.AddrPort, error) {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := data[1]
|
|
||||||
port := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
|
|
||||||
switch family {
|
|
||||||
case familyIPv4:
|
|
||||||
if len(data) < 8 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv4 too short")
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(
|
|
||||||
netip.AddrFrom4([4]byte{data[4], data[5], data[6], data[7]}), port,
|
|
||||||
), nil
|
|
||||||
case familyIPv6:
|
|
||||||
if len(data) < 20 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv6 too short")
|
|
||||||
}
|
|
||||||
var ip [16]byte
|
|
||||||
copy(ip[:], data[4:20])
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, E.New("unknown address family: ", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func roundTrip(conn net.PacketConn, addr net.Addr, txID TransactionID, attrs []stunAttribute, rto time.Duration) (*parsedResponse, time.Duration, error) {
|
|
||||||
request := buildBindingRequest(txID, attrs...)
|
|
||||||
currentRTO := rto
|
|
||||||
retransmitCount := 0
|
|
||||||
|
|
||||||
sendTime := time.Now()
|
|
||||||
_, err := conn.WriteTo(request, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "send STUN request")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
err = conn.SetReadDeadline(sendTime.Add(currentRTO))
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "set read deadline")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _, readErr := conn.ReadFrom(buf)
|
|
||||||
if readErr != nil {
|
|
||||||
if E.IsTimeout(readErr) && retransmitCount < maxRetransmit {
|
|
||||||
retransmitCount++
|
|
||||||
currentRTO *= 2
|
|
||||||
sendTime = time.Now()
|
|
||||||
_, err = conn.WriteTo(request, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "retransmit STUN request")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, 0, E.Cause(readErr, "read STUN response")
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < headerSize || buf[0]&0xC0 != 0 ||
|
|
||||||
binary.BigEndian.Uint32(buf[4:8]) != magicCookie {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var receivedTxID TransactionID
|
|
||||||
copy(receivedTxID[:], buf[8:20])
|
|
||||||
if receivedTxID != txID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
latency := time.Since(sendTime)
|
|
||||||
|
|
||||||
resp, parseErr := parseResponse(buf[:n], txID)
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, 0, parseErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, latency, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(options Options) (*Result, error) {
|
|
||||||
ctx := options.Context
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
server := options.Server
|
|
||||||
if server == "" {
|
|
||||||
server = DefaultServer
|
|
||||||
}
|
|
||||||
serverSocksaddr := M.ParseSocksaddr(server)
|
|
||||||
if serverSocksaddr.Port == 0 {
|
|
||||||
serverSocksaddr.Port = 3478
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress := options.OnProgress
|
|
||||||
if reportProgress == nil {
|
|
||||||
reportProgress = func(Progress) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
packetConn net.PacketConn
|
|
||||||
serverAddr net.Addr
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if options.Dialer != nil {
|
|
||||||
packetConn, err = options.Dialer.ListenPacket(ctx, serverSocksaddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create UDP socket")
|
|
||||||
}
|
|
||||||
serverAddr = serverSocksaddr
|
|
||||||
} else {
|
|
||||||
serverUDPAddr, resolveErr := net.ResolveUDPAddr("udp", serverSocksaddr.String())
|
|
||||||
if resolveErr != nil {
|
|
||||||
return nil, E.Cause(resolveErr, "resolve STUN server")
|
|
||||||
}
|
|
||||||
packetConn, err = net.ListenPacket("udp", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create UDP socket")
|
|
||||||
}
|
|
||||||
serverAddr = serverUDPAddr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = packetConn.Close()
|
|
||||||
}()
|
|
||||||
if deadline.NeedAdditionalReadDeadline(packetConn) {
|
|
||||||
packetConn = deadline.NewPacketConn(bufio.NewPacketConn(packetConn))
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
rto := defaultRTO
|
|
||||||
|
|
||||||
// Phase 1: Binding
|
|
||||||
reportProgress(Progress{Phase: PhaseBinding})
|
|
||||||
|
|
||||||
txID := newTransactionID()
|
|
||||||
resp, latency, err := roundTrip(packetConn, serverAddr, txID, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "binding request")
|
|
||||||
}
|
|
||||||
|
|
||||||
rto = max(minRTO, 3*latency)
|
|
||||||
|
|
||||||
externalAddr, ok := resp.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return nil, E.New("no mapped address in response")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Result{
|
|
||||||
ExternalAddr: externalAddr.String(),
|
|
||||||
LatencyMs: int32(latency.Milliseconds()),
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseBinding,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
|
|
||||||
otherAddr := resp.otherAddr
|
|
||||||
if !otherAddr.IsValid() {
|
|
||||||
result.NATTypeSupported = false
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseDone,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
result.NATTypeSupported = true
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: NAT Mapping Detection (RFC 5780 Section 4.3)
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATMapping,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
|
|
||||||
result.NATMapping = detectNATMapping(
|
|
||||||
packetConn, serverSocksaddr.Port, externalAddr, otherAddr, rto,
|
|
||||||
)
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATMapping,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
})
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: NAT Filtering Detection (RFC 5780 Section 4.4)
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATFiltering,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
})
|
|
||||||
|
|
||||||
result.NATFiltering = detectNATFiltering(packetConn, serverAddr, rto)
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseDone,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
NATFiltering: result.NATFiltering,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectNATMapping(
|
|
||||||
conn net.PacketConn,
|
|
||||||
serverPort uint16,
|
|
||||||
externalAddr netip.AddrPort,
|
|
||||||
otherAddr netip.AddrPort,
|
|
||||||
rto time.Duration,
|
|
||||||
) NATMapping {
|
|
||||||
// Mapping Test II: Send to other_ip:server_port
|
|
||||||
testIIAddr := net.UDPAddrFromAddrPort(
|
|
||||||
netip.AddrPortFrom(otherAddr.Addr(), serverPort),
|
|
||||||
)
|
|
||||||
txID2 := newTransactionID()
|
|
||||||
resp2, _, err := roundTrip(conn, testIIAddr, txID2, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
externalAddr2, ok := resp2.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAddr == externalAddr2 {
|
|
||||||
return NATMappingEndpointIndependent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mapping Test III: Send to other_ip:other_port
|
|
||||||
testIIIAddr := net.UDPAddrFromAddrPort(otherAddr)
|
|
||||||
txID3 := newTransactionID()
|
|
||||||
resp3, _, err := roundTrip(conn, testIIIAddr, txID3, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
externalAddr3, ok := resp3.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAddr2 == externalAddr3 {
|
|
||||||
return NATMappingAddressDependent
|
|
||||||
}
|
|
||||||
return NATMappingAddressAndPortDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectNATFiltering(
|
|
||||||
conn net.PacketConn,
|
|
||||||
serverAddr net.Addr,
|
|
||||||
rto time.Duration,
|
|
||||||
) NATFiltering {
|
|
||||||
// Filtering Test II: Request response from different IP and port
|
|
||||||
txID := newTransactionID()
|
|
||||||
_, _, err := roundTrip(conn, serverAddr, txID,
|
|
||||||
[]stunAttribute{changeRequestAttr(changeIP | changePort)}, rto)
|
|
||||||
if err == nil {
|
|
||||||
return NATFilteringEndpointIndependent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering Test III: Request response from different port only
|
|
||||||
txID = newTransactionID()
|
|
||||||
_, _, err = roundTrip(conn, serverAddr, txID,
|
|
||||||
[]stunAttribute{changeRequestAttr(changePort)}, rto)
|
|
||||||
if err == nil {
|
|
||||||
return NATFilteringAddressDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
return NATFilteringAddressAndPortDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
func paddingLen(n int) int {
|
|
||||||
if n%4 == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 4 - n%4
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/acmedns"
|
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
"github.com/libdns/cloudflare"
|
"github.com/libdns/cloudflare"
|
||||||
"github.com/mholt/acmez/v3/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
@@ -38,6 +37,37 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type acmeLogWriter struct {
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
||||||
|
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(logLine, "error: "):
|
||||||
|
w.logger.Error(logLine[7:])
|
||||||
|
case strings.HasPrefix(logLine, "warn: "):
|
||||||
|
w.logger.Warn(logLine[6:])
|
||||||
|
case strings.HasPrefix(logLine, "info: "):
|
||||||
|
w.logger.Info(logLine[6:])
|
||||||
|
case strings.HasPrefix(logLine, "debug: "):
|
||||||
|
w.logger.Debug(logLine[7:])
|
||||||
|
default:
|
||||||
|
w.logger.Debug(logLine)
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeLogWriter) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderConfig() zapcore.EncoderConfig {
|
||||||
|
config := zap.NewProductionEncoderConfig()
|
||||||
|
config.TimeKey = zapcore.OmitKey
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@@ -60,8 +90,8 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
zapLogger := zap.New(zapcore.NewCore(
|
||||||
zapcore.NewConsoleEncoder(ACMEEncoderConfig()),
|
zapcore.NewConsoleEncoder(encoderConfig()),
|
||||||
&ACMELogWriter{Logger: logger},
|
&acmeLogWriter{logger: logger},
|
||||||
zap.DebugLevel,
|
zap.DebugLevel,
|
||||||
))
|
))
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
@@ -96,13 +126,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
APIToken: dnsOptions.CloudflareOptions.APIToken,
|
APIToken: dnsOptions.CloudflareOptions.APIToken,
|
||||||
ZoneToken: dnsOptions.CloudflareOptions.ZoneToken,
|
ZoneToken: dnsOptions.CloudflareOptions.ZoneToken,
|
||||||
}
|
}
|
||||||
case C.DNSProviderACMEDNS:
|
|
||||||
solver.DNSProvider = &acmedns.Provider{
|
|
||||||
Username: dnsOptions.ACMEDNSOptions.Username,
|
|
||||||
Password: dnsOptions.ACMEDNSOptions.Password,
|
|
||||||
Subdomain: dnsOptions.ACMEDNSOptions.Subdomain,
|
|
||||||
ServerURL: dnsOptions.ACMEDNSOptions.ServerURL,
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
|
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
|
||||||
}
|
}
|
||||||
@@ -127,7 +150,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
GetCertificate: config.GetCertificate,
|
GetCertificate: config.GetCertificate,
|
||||||
NextProtos: []string{C.ACMETLS1Protocol},
|
NextProtos: []string{ACMETLS1Protocol},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package tls
|
||||||
|
|
||||||
const ACMETLS1Protocol = "acme-tls/1"
|
const ACMETLS1Protocol = "acme-tls/1"
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ACMELogWriter struct {
|
|
||||||
Logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ACMELogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.Logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.Logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.Logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.Logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.Logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ACMELogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ACMEEncoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
@@ -37,7 +38,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
}
|
}
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
||||||
return nil, E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0")
|
deprecated.Report(ctx, deprecated.OptionLegacyECHOptions)
|
||||||
}
|
}
|
||||||
if len(echConfig) > 0 {
|
if len(echConfig) > 0 {
|
||||||
block, rest := pem.Decode(echConfig)
|
block, rest := pem.Decode(echConfig)
|
||||||
@@ -76,7 +77,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
|
|||||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
|
||||||
return E.New("legacy ECH options are deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0")
|
deprecated.Report(ctx, deprecated.OptionLegacyECHOptions)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,6 @@ type RealityServerConfig struct {
|
|||||||
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
var tlsConfig utls.RealityConfig
|
var tlsConfig utls.RealityConfig
|
||||||
|
|
||||||
if options.CertificateProvider != nil {
|
|
||||||
return nil, E.New("certificate_provider is unavailable in reality")
|
|
||||||
}
|
|
||||||
//nolint:staticcheck
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,87 +13,19 @@ import (
|
|||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"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/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/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInsecureUnused = E.New("tls: insecure unused")
|
var errInsecureUnused = E.New("tls: insecure unused")
|
||||||
|
|
||||||
type managedCertificateProvider interface {
|
|
||||||
adapter.CertificateProvider
|
|
||||||
adapter.SimpleLifecycle
|
|
||||||
}
|
|
||||||
|
|
||||||
type sharedCertificateProvider struct {
|
|
||||||
tag string
|
|
||||||
manager adapter.CertificateProviderManager
|
|
||||||
provider adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) Start() error {
|
|
||||||
provider, found := p.manager.Get(p.tag)
|
|
||||||
if !found {
|
|
||||||
return E.New("certificate provider not found: ", p.tag)
|
|
||||||
}
|
|
||||||
p.provider = provider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
return p.provider.GetCertificate(hello)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *sharedCertificateProvider) GetACMENextProtos() []string {
|
|
||||||
return getACMENextProtos(p.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
type inlineCertificateProvider struct {
|
|
||||||
provider adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) Start() error {
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
err := adapter.LegacyStart(p.provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) Close() error {
|
|
||||||
return p.provider.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
return p.provider.GetCertificate(hello)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *inlineCertificateProvider) GetACMENextProtos() []string {
|
|
||||||
return getACMENextProtos(p.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getACMENextProtos(provider adapter.CertificateProvider) []string {
|
|
||||||
if acmeProvider, isACME := provider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
return acmeProvider.GetACMENextProtos()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
certificateProvider managedCertificateProvider
|
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.SimpleLifecycle
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
@@ -121,17 +53,18 @@ func (c *STDServerConfig) SetServerName(serverName string) {
|
|||||||
func (c *STDServerConfig) NextProtos() []string {
|
func (c *STDServerConfig) NextProtos() []string {
|
||||||
c.access.RLock()
|
c.access.RLock()
|
||||||
defer c.access.RUnlock()
|
defer c.access.RUnlock()
|
||||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
return c.config.NextProtos[1:]
|
return c.config.NextProtos[1:]
|
||||||
}
|
} else {
|
||||||
return c.config.NextProtos
|
return c.config.NextProtos
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
config := c.config.Clone()
|
config := c.config.Clone()
|
||||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||||
} else {
|
} else {
|
||||||
config.NextProtos = nextProto
|
config.NextProtos = nextProto
|
||||||
@@ -139,18 +72,6 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
|||||||
c.config = config
|
c.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) hasACMEALPN() bool {
|
|
||||||
if c.acmeService != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if c.certificateProvider != nil {
|
|
||||||
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
return len(acmeProvider.GetACMENextProtos()) > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
|
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
@@ -170,39 +91,15 @@ func (c *STDServerConfig) Clone() Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Start() error {
|
func (c *STDServerConfig) Start() error {
|
||||||
if c.certificateProvider != nil {
|
|
||||||
err := c.certificateProvider.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
nextProtos := acmeProvider.GetACMENextProtos()
|
|
||||||
if len(nextProtos) > 0 {
|
|
||||||
c.access.Lock()
|
|
||||||
config := c.config.Clone()
|
|
||||||
mergedNextProtos := append([]string{}, nextProtos...)
|
|
||||||
for _, nextProto := range config.NextProtos {
|
|
||||||
if !common.Contains(mergedNextProtos, nextProto) {
|
|
||||||
mergedNextProtos = append(mergedNextProtos, nextProto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.NextProtos = mergedNextProtos
|
|
||||||
c.config = config
|
|
||||||
c.access.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.acmeService != nil {
|
if c.acmeService != nil {
|
||||||
err := c.acmeService.Start()
|
return c.acmeService.Start()
|
||||||
if err != nil {
|
} else {
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := c.startWatcher()
|
err := c.startWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) startWatcher() error {
|
func (c *STDServerConfig) startWatcher() error {
|
||||||
@@ -306,34 +203,23 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Close() error {
|
func (c *STDServerConfig) Close() error {
|
||||||
return common.Close(c.certificateProvider, c.acmeService, c.watcher)
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Close()
|
||||||
|
}
|
||||||
|
if c.watcher != nil {
|
||||||
|
return c.watcher.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
//nolint:staticcheck
|
|
||||||
if options.CertificateProvider != nil && options.ACME != nil {
|
|
||||||
return nil, E.New("certificate_provider and acme are mutually exclusive")
|
|
||||||
}
|
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var certificateProvider managedCertificateProvider
|
|
||||||
var acmeService adapter.SimpleLifecycle
|
var acmeService adapter.SimpleLifecycle
|
||||||
var err error
|
var err error
|
||||||
if options.CertificateProvider != nil {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
certificateProvider, err = newCertificateProvider(ctx, logger, options.CertificateProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig = &tls.Config{
|
|
||||||
GetCertificate: certificateProvider.GetCertificate,
|
|
||||||
}
|
|
||||||
if options.Insecure {
|
|
||||||
return nil, errInsecureUnused
|
|
||||||
}
|
|
||||||
} else if options.ACME != nil && len(options.ACME.Domain) > 0 { //nolint:staticcheck
|
|
||||||
deprecated.Report(ctx, deprecated.OptionInlineACME)
|
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -386,7 +272,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
)
|
)
|
||||||
if certificateProvider == nil && acmeService == nil {
|
if acmeService == nil {
|
||||||
if len(options.Certificate) > 0 {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
@@ -474,7 +360,6 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
serverConfig := &STDServerConfig{
|
serverConfig := &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
certificateProvider: certificateProvider,
|
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
key: key,
|
key: key,
|
||||||
@@ -484,8 +369,8 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
echKeyPath: echKeyPath,
|
echKeyPath: echKeyPath,
|
||||||
}
|
}
|
||||||
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
serverConfig.access.RLock()
|
serverConfig.access.Lock()
|
||||||
defer serverConfig.access.RUnlock()
|
defer serverConfig.access.Unlock()
|
||||||
return serverConfig.config, nil
|
return serverConfig.config, nil
|
||||||
}
|
}
|
||||||
var config ServerConfig = serverConfig
|
var config ServerConfig = serverConfig
|
||||||
@@ -502,27 +387,3 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCertificateProvider(ctx context.Context, logger log.ContextLogger, options *option.CertificateProviderOptions) (managedCertificateProvider, error) {
|
|
||||||
if options.IsShared() {
|
|
||||||
manager := service.FromContext[adapter.CertificateProviderManager](ctx)
|
|
||||||
if manager == nil {
|
|
||||||
return nil, E.New("missing certificate provider manager in context")
|
|
||||||
}
|
|
||||||
return &sharedCertificateProvider{
|
|
||||||
tag: options.Tag,
|
|
||||||
manager: manager,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
registry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
|
|
||||||
if registry == nil {
|
|
||||||
return nil, E.New("missing certificate provider registry in context")
|
|
||||||
}
|
|
||||||
provider, err := registry.Create(ctx, logger, "", options.Type, options.Options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create inline certificate provider")
|
|
||||||
}
|
|
||||||
return &inlineCertificateProvider{
|
|
||||||
provider: provider,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DNSTypeLegacy = "legacy"
|
DNSTypeLegacy = "legacy"
|
||||||
|
DNSTypeLegacyRcode = "legacy_rcode"
|
||||||
DNSTypeUDP = "udp"
|
DNSTypeUDP = "udp"
|
||||||
DNSTypeTCP = "tcp"
|
DNSTypeTCP = "tcp"
|
||||||
DNSTypeTLS = "tls"
|
DNSTypeTLS = "tls"
|
||||||
@@ -32,5 +33,4 @@ const (
|
|||||||
const (
|
const (
|
||||||
DNSProviderAliDNS = "alidns"
|
DNSProviderAliDNS = "alidns"
|
||||||
DNSProviderCloudflare = "cloudflare"
|
DNSProviderCloudflare = "cloudflare"
|
||||||
DNSProviderACMEDNS = "acmedns"
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,15 +25,11 @@ const (
|
|||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeCloudflared = "cloudflared"
|
|
||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
TypeCCM = "ccm"
|
TypeCCM = "ccm"
|
||||||
TypeOCM = "ocm"
|
TypeOCM = "ocm"
|
||||||
TypeOOMKiller = "oom-killer"
|
|
||||||
TypeACME = "acme"
|
|
||||||
TypeCloudflareOriginCA = "cloudflare-origin-ca"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -89,10 +85,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeTailscale:
|
|
||||||
return "Tailscale"
|
|
||||||
case TypeCloudflared:
|
|
||||||
return "Cloudflared"
|
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
|
|||||||
@@ -23,15 +23,12 @@ const (
|
|||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersion4
|
RuleSetVersion4
|
||||||
RuleSetVersion5
|
RuleSetVersionCurrent = RuleSetVersion4
|
||||||
RuleSetVersionCurrent = RuleSetVersion5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RuleActionTypeRoute = "route"
|
RuleActionTypeRoute = "route"
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
RuleActionTypeRouteOptions = "route-options"
|
||||||
RuleActionTypeEvaluate = "evaluate"
|
|
||||||
RuleActionTypeRespond = "respond"
|
|
||||||
RuleActionTypeDirect = "direct"
|
RuleActionTypeDirect = "direct"
|
||||||
RuleActionTypeBypass = "bypass"
|
RuleActionTypeBypass = "bypass"
|
||||||
RuleActionTypeReject = "reject"
|
RuleActionTypeReject = "reject"
|
||||||
|
|||||||
702
daemon/helper.pb.go
Normal file
702
daemon/helper.pb.go
Normal file
@@ -0,0 +1,702 @@
|
|||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubscribeHelperRequestRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
AcceptGetWIFIStateRequests bool `protobuf:"varint,1,opt,name=acceptGetWIFIStateRequests,proto3" json:"acceptGetWIFIStateRequests,omitempty"`
|
||||||
|
AcceptFindConnectionOwnerRequests bool `protobuf:"varint,2,opt,name=acceptFindConnectionOwnerRequests,proto3" json:"acceptFindConnectionOwnerRequests,omitempty"`
|
||||||
|
AcceptSendNotificationRequests bool `protobuf:"varint,3,opt,name=acceptSendNotificationRequests,proto3" json:"acceptSendNotificationRequests,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SubscribeHelperRequestRequest) Reset() {
|
||||||
|
*x = SubscribeHelperRequestRequest{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SubscribeHelperRequestRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SubscribeHelperRequestRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SubscribeHelperRequestRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use SubscribeHelperRequestRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SubscribeHelperRequestRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SubscribeHelperRequestRequest) GetAcceptGetWIFIStateRequests() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.AcceptGetWIFIStateRequests
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SubscribeHelperRequestRequest) GetAcceptFindConnectionOwnerRequests() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.AcceptFindConnectionOwnerRequests
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SubscribeHelperRequestRequest) GetAcceptSendNotificationRequests() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.AcceptSendNotificationRequests
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
// Types that are valid to be assigned to Request:
|
||||||
|
//
|
||||||
|
// *HelperRequest_GetWIFIState
|
||||||
|
// *HelperRequest_FindConnectionOwner
|
||||||
|
// *HelperRequest_SendNotification
|
||||||
|
Request isHelperRequest_Request `protobuf_oneof:"request"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) Reset() {
|
||||||
|
*x = HelperRequest{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HelperRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *HelperRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use HelperRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*HelperRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) GetId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) GetRequest() isHelperRequest_Request {
|
||||||
|
if x != nil {
|
||||||
|
return x.Request
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) GetGetWIFIState() *emptypb.Empty {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Request.(*HelperRequest_GetWIFIState); ok {
|
||||||
|
return x.GetWIFIState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) GetFindConnectionOwner() *FindConnectionOwnerRequest {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Request.(*HelperRequest_FindConnectionOwner); ok {
|
||||||
|
return x.FindConnectionOwner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperRequest) GetSendNotification() *Notification {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Request.(*HelperRequest_SendNotification); ok {
|
||||||
|
return x.SendNotification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isHelperRequest_Request interface {
|
||||||
|
isHelperRequest_Request()
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperRequest_GetWIFIState struct {
|
||||||
|
GetWIFIState *emptypb.Empty `protobuf:"bytes,2,opt,name=getWIFIState,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperRequest_FindConnectionOwner struct {
|
||||||
|
FindConnectionOwner *FindConnectionOwnerRequest `protobuf:"bytes,3,opt,name=findConnectionOwner,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperRequest_SendNotification struct {
|
||||||
|
SendNotification *Notification `protobuf:"bytes,4,opt,name=sendNotification,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HelperRequest_GetWIFIState) isHelperRequest_Request() {}
|
||||||
|
|
||||||
|
func (*HelperRequest_FindConnectionOwner) isHelperRequest_Request() {}
|
||||||
|
|
||||||
|
func (*HelperRequest_SendNotification) isHelperRequest_Request() {}
|
||||||
|
|
||||||
|
type FindConnectionOwnerRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
IpProtocol int32 `protobuf:"varint,1,opt,name=ipProtocol,proto3" json:"ipProtocol,omitempty"`
|
||||||
|
SourceAddress string `protobuf:"bytes,2,opt,name=sourceAddress,proto3" json:"sourceAddress,omitempty"`
|
||||||
|
SourcePort int32 `protobuf:"varint,3,opt,name=sourcePort,proto3" json:"sourcePort,omitempty"`
|
||||||
|
DestinationAddress string `protobuf:"bytes,4,opt,name=destinationAddress,proto3" json:"destinationAddress,omitempty"`
|
||||||
|
DestinationPort int32 `protobuf:"varint,5,opt,name=destinationPort,proto3" json:"destinationPort,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) Reset() {
|
||||||
|
*x = FindConnectionOwnerRequest{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*FindConnectionOwnerRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[2]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use FindConnectionOwnerRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*FindConnectionOwnerRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) GetIpProtocol() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.IpProtocol
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) GetSourceAddress() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.SourceAddress
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) GetSourcePort() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.SourcePort
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) GetDestinationAddress() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.DestinationAddress
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *FindConnectionOwnerRequest) GetDestinationPort() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.DestinationPort
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notification struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
|
||||||
|
TypeName string `protobuf:"bytes,2,opt,name=typeName,proto3" json:"typeName,omitempty"`
|
||||||
|
TypeId int32 `protobuf:"varint,3,opt,name=typeId,proto3" json:"typeId,omitempty"`
|
||||||
|
Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"`
|
||||||
|
Subtitle string `protobuf:"bytes,5,opt,name=subtitle,proto3" json:"subtitle,omitempty"`
|
||||||
|
Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
|
||||||
|
OpenURL string `protobuf:"bytes,7,opt,name=openURL,proto3" json:"openURL,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) Reset() {
|
||||||
|
*x = Notification{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Notification) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Notification) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[3]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Notification.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Notification) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetIdentifier() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Identifier
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetTypeName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.TypeName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetTypeId() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.TypeId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetTitle() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Title
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetSubtitle() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Subtitle
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetBody() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Body
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Notification) GetOpenURL() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.OpenURL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
// Types that are valid to be assigned to Response:
|
||||||
|
//
|
||||||
|
// *HelperResponse_WifiState
|
||||||
|
// *HelperResponse_Error
|
||||||
|
// *HelperResponse_ConnectionOwner
|
||||||
|
Response isHelperResponse_Response `protobuf_oneof:"response"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) Reset() {
|
||||||
|
*x = HelperResponse{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HelperResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *HelperResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[4]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use HelperResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*HelperResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) GetId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) GetResponse() isHelperResponse_Response {
|
||||||
|
if x != nil {
|
||||||
|
return x.Response
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) GetWifiState() *WIFIState {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Response.(*HelperResponse_WifiState); ok {
|
||||||
|
return x.WifiState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) GetError() string {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Response.(*HelperResponse_Error); ok {
|
||||||
|
return x.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *HelperResponse) GetConnectionOwner() *ConnectionOwner {
|
||||||
|
if x != nil {
|
||||||
|
if x, ok := x.Response.(*HelperResponse_ConnectionOwner); ok {
|
||||||
|
return x.ConnectionOwner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isHelperResponse_Response interface {
|
||||||
|
isHelperResponse_Response()
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperResponse_WifiState struct {
|
||||||
|
WifiState *WIFIState `protobuf:"bytes,2,opt,name=wifiState,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperResponse_Error struct {
|
||||||
|
Error string `protobuf:"bytes,3,opt,name=error,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelperResponse_ConnectionOwner struct {
|
||||||
|
ConnectionOwner *ConnectionOwner `protobuf:"bytes,4,opt,name=connectionOwner,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*HelperResponse_WifiState) isHelperResponse_Response() {}
|
||||||
|
|
||||||
|
func (*HelperResponse_Error) isHelperResponse_Response() {}
|
||||||
|
|
||||||
|
func (*HelperResponse_ConnectionOwner) isHelperResponse_Response() {}
|
||||||
|
|
||||||
|
type ConnectionOwner struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"`
|
||||||
|
UserName string `protobuf:"bytes,2,opt,name=userName,proto3" json:"userName,omitempty"`
|
||||||
|
ProcessPath string `protobuf:"bytes,3,opt,name=processPath,proto3" json:"processPath,omitempty"`
|
||||||
|
AndroidPackageName string `protobuf:"bytes,4,opt,name=androidPackageName,proto3" json:"androidPackageName,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) Reset() {
|
||||||
|
*x = ConnectionOwner{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ConnectionOwner) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[5]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionOwner.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ConnectionOwner) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) GetUserId() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) GetUserName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) GetProcessPath() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ProcessPath
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ConnectionOwner) GetAndroidPackageName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AndroidPackageName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type WIFIState struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Ssid string `protobuf:"bytes,1,opt,name=ssid,proto3" json:"ssid,omitempty"`
|
||||||
|
Bssid string `protobuf:"bytes,2,opt,name=bssid,proto3" json:"bssid,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *WIFIState) Reset() {
|
||||||
|
*x = WIFIState{}
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *WIFIState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*WIFIState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *WIFIState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_helper_proto_msgTypes[6]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use WIFIState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*WIFIState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_helper_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *WIFIState) GetSsid() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ssid
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *WIFIState) GetBssid() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Bssid
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_daemon_helper_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_daemon_helper_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"\x13daemon/helper.proto\x12\x06daemon\x1a\x1bgoogle/protobuf/empty.proto\"\xf5\x01\n" +
|
||||||
|
"\x1dSubscribeHelperRequestRequest\x12>\n" +
|
||||||
|
"\x1aacceptGetWIFIStateRequests\x18\x01 \x01(\bR\x1aacceptGetWIFIStateRequests\x12L\n" +
|
||||||
|
"!acceptFindConnectionOwnerRequests\x18\x02 \x01(\bR!acceptFindConnectionOwnerRequests\x12F\n" +
|
||||||
|
"\x1eacceptSendNotificationRequests\x18\x03 \x01(\bR\x1eacceptSendNotificationRequests\"\x84\x02\n" +
|
||||||
|
"\rHelperRequest\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x03R\x02id\x12<\n" +
|
||||||
|
"\fgetWIFIState\x18\x02 \x01(\v2\x16.google.protobuf.EmptyH\x00R\fgetWIFIState\x12V\n" +
|
||||||
|
"\x13findConnectionOwner\x18\x03 \x01(\v2\".daemon.FindConnectionOwnerRequestH\x00R\x13findConnectionOwner\x12B\n" +
|
||||||
|
"\x10sendNotification\x18\x04 \x01(\v2\x14.daemon.NotificationH\x00R\x10sendNotificationB\t\n" +
|
||||||
|
"\arequest\"\xdc\x01\n" +
|
||||||
|
"\x1aFindConnectionOwnerRequest\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"ipProtocol\x18\x01 \x01(\x05R\n" +
|
||||||
|
"ipProtocol\x12$\n" +
|
||||||
|
"\rsourceAddress\x18\x02 \x01(\tR\rsourceAddress\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"sourcePort\x18\x03 \x01(\x05R\n" +
|
||||||
|
"sourcePort\x12.\n" +
|
||||||
|
"\x12destinationAddress\x18\x04 \x01(\tR\x12destinationAddress\x12(\n" +
|
||||||
|
"\x0fdestinationPort\x18\x05 \x01(\x05R\x0fdestinationPort\"\xc2\x01\n" +
|
||||||
|
"\fNotification\x12\x1e\n" +
|
||||||
|
"\n" +
|
||||||
|
"identifier\x18\x01 \x01(\tR\n" +
|
||||||
|
"identifier\x12\x1a\n" +
|
||||||
|
"\btypeName\x18\x02 \x01(\tR\btypeName\x12\x16\n" +
|
||||||
|
"\x06typeId\x18\x03 \x01(\x05R\x06typeId\x12\x14\n" +
|
||||||
|
"\x05title\x18\x04 \x01(\tR\x05title\x12\x1a\n" +
|
||||||
|
"\bsubtitle\x18\x05 \x01(\tR\bsubtitle\x12\x12\n" +
|
||||||
|
"\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" +
|
||||||
|
"\aopenURL\x18\a \x01(\tR\aopenURL\"\xbc\x01\n" +
|
||||||
|
"\x0eHelperResponse\x12\x0e\n" +
|
||||||
|
"\x02id\x18\x01 \x01(\x03R\x02id\x121\n" +
|
||||||
|
"\twifiState\x18\x02 \x01(\v2\x11.daemon.WIFIStateH\x00R\twifiState\x12\x16\n" +
|
||||||
|
"\x05error\x18\x03 \x01(\tH\x00R\x05error\x12C\n" +
|
||||||
|
"\x0fconnectionOwner\x18\x04 \x01(\v2\x17.daemon.ConnectionOwnerH\x00R\x0fconnectionOwnerB\n" +
|
||||||
|
"\n" +
|
||||||
|
"\bresponse\"\x97\x01\n" +
|
||||||
|
"\x0fConnectionOwner\x12\x16\n" +
|
||||||
|
"\x06userId\x18\x01 \x01(\x05R\x06userId\x12\x1a\n" +
|
||||||
|
"\buserName\x18\x02 \x01(\tR\buserName\x12 \n" +
|
||||||
|
"\vprocessPath\x18\x03 \x01(\tR\vprocessPath\x12.\n" +
|
||||||
|
"\x12androidPackageName\x18\x04 \x01(\tR\x12androidPackageName\"5\n" +
|
||||||
|
"\tWIFIState\x12\x12\n" +
|
||||||
|
"\x04ssid\x18\x01 \x01(\tR\x04ssid\x12\x14\n" +
|
||||||
|
"\x05bssid\x18\x02 \x01(\tR\x05bssidB%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_daemon_helper_proto_rawDescOnce sync.Once
|
||||||
|
file_daemon_helper_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_daemon_helper_proto_rawDescGZIP() []byte {
|
||||||
|
file_daemon_helper_proto_rawDescOnce.Do(func() {
|
||||||
|
file_daemon_helper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_daemon_helper_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_daemon_helper_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||||
|
file_daemon_helper_proto_goTypes = []any{
|
||||||
|
(*SubscribeHelperRequestRequest)(nil), // 0: daemon.SubscribeHelperRequestRequest
|
||||||
|
(*HelperRequest)(nil), // 1: daemon.HelperRequest
|
||||||
|
(*FindConnectionOwnerRequest)(nil), // 2: daemon.FindConnectionOwnerRequest
|
||||||
|
(*Notification)(nil), // 3: daemon.Notification
|
||||||
|
(*HelperResponse)(nil), // 4: daemon.HelperResponse
|
||||||
|
(*ConnectionOwner)(nil), // 5: daemon.ConnectionOwner
|
||||||
|
(*WIFIState)(nil), // 6: daemon.WIFIState
|
||||||
|
(*emptypb.Empty)(nil), // 7: google.protobuf.Empty
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var file_daemon_helper_proto_depIdxs = []int32{
|
||||||
|
7, // 0: daemon.HelperRequest.getWIFIState:type_name -> google.protobuf.Empty
|
||||||
|
2, // 1: daemon.HelperRequest.findConnectionOwner:type_name -> daemon.FindConnectionOwnerRequest
|
||||||
|
3, // 2: daemon.HelperRequest.sendNotification:type_name -> daemon.Notification
|
||||||
|
6, // 3: daemon.HelperResponse.wifiState:type_name -> daemon.WIFIState
|
||||||
|
5, // 4: daemon.HelperResponse.connectionOwner:type_name -> daemon.ConnectionOwner
|
||||||
|
5, // [5:5] is the sub-list for method output_type
|
||||||
|
5, // [5:5] is the sub-list for method input_type
|
||||||
|
5, // [5:5] is the sub-list for extension type_name
|
||||||
|
5, // [5:5] is the sub-list for extension extendee
|
||||||
|
0, // [0:5] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_daemon_helper_proto_init() }
|
||||||
|
func file_daemon_helper_proto_init() {
|
||||||
|
if File_daemon_helper_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file_daemon_helper_proto_msgTypes[1].OneofWrappers = []any{
|
||||||
|
(*HelperRequest_GetWIFIState)(nil),
|
||||||
|
(*HelperRequest_FindConnectionOwner)(nil),
|
||||||
|
(*HelperRequest_SendNotification)(nil),
|
||||||
|
}
|
||||||
|
file_daemon_helper_proto_msgTypes[4].OneofWrappers = []any{
|
||||||
|
(*HelperResponse_WifiState)(nil),
|
||||||
|
(*HelperResponse_Error)(nil),
|
||||||
|
(*HelperResponse_ConnectionOwner)(nil),
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_helper_proto_rawDesc), len(file_daemon_helper_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 7,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_daemon_helper_proto_goTypes,
|
||||||
|
DependencyIndexes: file_daemon_helper_proto_depIdxs,
|
||||||
|
MessageInfos: file_daemon_helper_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_daemon_helper_proto = out.File
|
||||||
|
file_daemon_helper_proto_goTypes = nil
|
||||||
|
file_daemon_helper_proto_depIdxs = nil
|
||||||
|
}
|
||||||
61
daemon/helper.proto
Normal file
61
daemon/helper.proto
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package daemon;
|
||||||
|
option go_package = "github.com/sagernet/sing-box/daemon";
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
message SubscribeHelperRequestRequest {
|
||||||
|
bool acceptGetWIFIStateRequests = 1;
|
||||||
|
bool acceptFindConnectionOwnerRequests = 2;
|
||||||
|
bool acceptSendNotificationRequests = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HelperRequest {
|
||||||
|
int64 id = 1;
|
||||||
|
oneof request {
|
||||||
|
google.protobuf.Empty getWIFIState = 2;
|
||||||
|
FindConnectionOwnerRequest findConnectionOwner = 3;
|
||||||
|
Notification sendNotification = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message FindConnectionOwnerRequest {
|
||||||
|
int32 ipProtocol = 1;
|
||||||
|
string sourceAddress = 2;
|
||||||
|
int32 sourcePort = 3;
|
||||||
|
string destinationAddress = 4;
|
||||||
|
int32 destinationPort = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Notification {
|
||||||
|
string identifier = 1;
|
||||||
|
string typeName = 2;
|
||||||
|
int32 typeId = 3;
|
||||||
|
string title = 4;
|
||||||
|
string subtitle = 5;
|
||||||
|
string body = 6;
|
||||||
|
string openURL = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HelperResponse {
|
||||||
|
int64 id = 1;
|
||||||
|
oneof response {
|
||||||
|
WIFIState wifiState = 2;
|
||||||
|
string error = 3;
|
||||||
|
ConnectionOwner connectionOwner = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionOwner {
|
||||||
|
int32 userId = 1;
|
||||||
|
string userName = 2;
|
||||||
|
string processPath = 3;
|
||||||
|
string androidPackageName = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message WIFIState {
|
||||||
|
string ssid = 1;
|
||||||
|
string bssid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -7,12 +7,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/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/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -23,7 +20,6 @@ type Instance struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
instance *box.Box
|
instance *box.Box
|
||||||
connectionManager adapter.ConnectionManager
|
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
cacheFile adapter.CacheFile
|
cacheFile adapter.CacheFile
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
@@ -87,20 +83,6 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.oomKillerEnabled {
|
|
||||||
if !common.Any(options.Services, func(it option.Service) bool {
|
|
||||||
return it.Type == C.TypeOOMKiller
|
|
||||||
}) {
|
|
||||||
oomOptions := &option.OOMKillerServiceOptions{
|
|
||||||
KillerDisabled: s.oomKillerDisabled,
|
|
||||||
MemoryLimitOverride: s.oomMemoryLimit,
|
|
||||||
}
|
|
||||||
options.Services = append(options.Services, option.Service{
|
|
||||||
Type: C.TypeOOMKiller,
|
|
||||||
Options: oomOptions,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
i := &Instance{
|
i := &Instance{
|
||||||
@@ -118,11 +100,9 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
i.instance = boxInstance
|
i.instance = boxInstance
|
||||||
i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx)
|
|
||||||
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
|
||||||
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
i.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||||
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
|
||||||
log.SetStdLogger(boxInstance.LogFactory().Logger())
|
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ type PlatformHandler interface {
|
|||||||
ServiceReload() error
|
ServiceReload() error
|
||||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(enabled bool) error
|
SetSystemProxyEnabled(enabled bool) error
|
||||||
TriggerNativeCrash() error
|
|
||||||
WriteDebugMessage(message string)
|
WriteDebugMessage(message string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,15 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing-box/service/oomkiller"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -30,8 +25,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,9 +36,6 @@ type StartedService struct {
|
|||||||
handler PlatformHandler
|
handler PlatformHandler
|
||||||
debug bool
|
debug bool
|
||||||
logMaxLines int
|
logMaxLines int
|
||||||
oomKillerEnabled bool
|
|
||||||
oomKillerDisabled bool
|
|
||||||
oomMemoryLimit uint64
|
|
||||||
// workingDirectory string
|
// workingDirectory string
|
||||||
// tempDirectory string
|
// tempDirectory string
|
||||||
// userID int
|
// userID int
|
||||||
@@ -66,9 +56,6 @@ type StartedService struct {
|
|||||||
urlTestHistoryStorage *urltest.HistoryStorage
|
urlTestHistoryStorage *urltest.HistoryStorage
|
||||||
clashModeSubscriber *observable.Subscriber[struct{}]
|
clashModeSubscriber *observable.Subscriber[struct{}]
|
||||||
clashModeObserver *observable.Observer[struct{}]
|
clashModeObserver *observable.Observer[struct{}]
|
||||||
|
|
||||||
connectionEventSubscriber *observable.Subscriber[trafficontrol.ConnectionEvent]
|
|
||||||
connectionEventObserver *observable.Observer[trafficontrol.ConnectionEvent]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceOptions struct {
|
type ServiceOptions struct {
|
||||||
@@ -77,9 +64,6 @@ type ServiceOptions struct {
|
|||||||
Handler PlatformHandler
|
Handler PlatformHandler
|
||||||
Debug bool
|
Debug bool
|
||||||
LogMaxLines int
|
LogMaxLines int
|
||||||
OOMKillerEnabled bool
|
|
||||||
OOMKillerDisabled bool
|
|
||||||
OOMMemoryLimit uint64
|
|
||||||
// WorkingDirectory string
|
// WorkingDirectory string
|
||||||
// TempDirectory string
|
// TempDirectory string
|
||||||
// UserID int
|
// UserID int
|
||||||
@@ -94,9 +78,6 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
|||||||
handler: options.Handler,
|
handler: options.Handler,
|
||||||
debug: options.Debug,
|
debug: options.Debug,
|
||||||
logMaxLines: options.LogMaxLines,
|
logMaxLines: options.LogMaxLines,
|
||||||
oomKillerEnabled: options.OOMKillerEnabled,
|
|
||||||
oomKillerDisabled: options.OOMKillerDisabled,
|
|
||||||
oomMemoryLimit: options.OOMMemoryLimit,
|
|
||||||
// workingDirectory: options.WorkingDirectory,
|
// workingDirectory: options.WorkingDirectory,
|
||||||
// tempDirectory: options.TempDirectory,
|
// tempDirectory: options.TempDirectory,
|
||||||
// userID: options.UserID,
|
// userID: options.UserID,
|
||||||
@@ -108,13 +89,11 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
|||||||
urlTestSubscriber: observable.NewSubscriber[struct{}](1),
|
urlTestSubscriber: observable.NewSubscriber[struct{}](1),
|
||||||
urlTestHistoryStorage: urltest.NewHistoryStorage(),
|
urlTestHistoryStorage: urltest.NewHistoryStorage(),
|
||||||
clashModeSubscriber: observable.NewSubscriber[struct{}](1),
|
clashModeSubscriber: observable.NewSubscriber[struct{}](1),
|
||||||
connectionEventSubscriber: observable.NewSubscriber[trafficontrol.ConnectionEvent](256),
|
|
||||||
}
|
}
|
||||||
s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2)
|
s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2)
|
||||||
s.logObserver = observable.NewObserver(s.logSubscriber, 64)
|
s.logObserver = observable.NewObserver(s.logSubscriber, 64)
|
||||||
s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1)
|
s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1)
|
||||||
s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1)
|
s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1)
|
||||||
s.connectionEventObserver = observable.NewObserver(s.connectionEventSubscriber, 64)
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +161,7 @@ func (s *StartedService) waitForStarted(ctx context.Context) error {
|
|||||||
func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {
|
func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {
|
||||||
s.serviceAccess.Lock()
|
s.serviceAccess.Lock()
|
||||||
switch s.serviceStatus.Status {
|
switch s.serviceStatus.Status {
|
||||||
case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING, ServiceStatus_FATAL:
|
case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING:
|
||||||
default:
|
default:
|
||||||
s.serviceAccess.Unlock()
|
s.serviceAccess.Unlock()
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
@@ -204,7 +183,6 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov
|
|||||||
instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber)
|
instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber)
|
||||||
if instance.clashServer != nil {
|
if instance.clashServer != nil {
|
||||||
instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber)
|
instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber)
|
||||||
instance.clashServer.(*clashapi.Server).TrafficManager().SetEventHook(s.connectionEventSubscriber)
|
|
||||||
}
|
}
|
||||||
s.serviceAccess.Unlock()
|
s.serviceAccess.Unlock()
|
||||||
err = instance.Start()
|
err = instance.Start()
|
||||||
@@ -223,14 +201,6 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) Close() {
|
|
||||||
s.serviceStatusSubscriber.Close()
|
|
||||||
s.logSubscriber.Close()
|
|
||||||
s.urlTestSubscriber.Close()
|
|
||||||
s.clashModeSubscriber.Close()
|
|
||||||
s.connectionEventSubscriber.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) CloseService() error {
|
func (s *StartedService) CloseService() error {
|
||||||
s.serviceAccess.Lock()
|
s.serviceAccess.Lock()
|
||||||
switch s.serviceStatus.Status {
|
switch s.serviceStatus.Status {
|
||||||
@@ -240,14 +210,13 @@ func (s *StartedService) CloseService() error {
|
|||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
s.updateStatus(ServiceStatus_STOPPING)
|
s.updateStatus(ServiceStatus_STOPPING)
|
||||||
instance := s.instance
|
if s.instance != nil {
|
||||||
s.instance = nil
|
err := s.instance.Close()
|
||||||
if instance != nil {
|
|
||||||
err := instance.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s.updateStatusError(err)
|
return s.updateStatusError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.instance = nil
|
||||||
s.startedAt = time.Time{}
|
s.startedAt = time.Time{}
|
||||||
s.updateStatus(ServiceStatus_IDLE)
|
s.updateStatus(ServiceStatus_IDLE)
|
||||||
s.serviceAccess.Unlock()
|
s.serviceAccess.Unlock()
|
||||||
@@ -424,14 +393,12 @@ func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server
|
|||||||
|
|
||||||
func (s *StartedService) readStatus() *Status {
|
func (s *StartedService) readStatus() *Status {
|
||||||
var status Status
|
var status Status
|
||||||
status.Memory = memory.Total()
|
status.Memory = memory.Inuse()
|
||||||
status.Goroutines = int32(runtime.NumGoroutine())
|
status.Goroutines = int32(runtime.NumGoroutine())
|
||||||
|
status.ConnectionsOut = int32(conntrack.Count())
|
||||||
s.serviceAccess.RLock()
|
s.serviceAccess.RLock()
|
||||||
nowService := s.instance
|
nowService := s.instance
|
||||||
s.serviceAccess.RUnlock()
|
s.serviceAccess.RUnlock()
|
||||||
if nowService != nil && nowService.connectionManager != nil {
|
|
||||||
status.ConnectionsOut = int32(nowService.connectionManager.Count())
|
|
||||||
}
|
|
||||||
if nowService != nil {
|
if nowService != nil {
|
||||||
if clashServer := nowService.clashServer; clashServer != nil {
|
if clashServer := nowService.clashServer; clashServer != nil {
|
||||||
status.TrafficAvailable = true
|
status.TrafficAvailable = true
|
||||||
@@ -696,45 +663,10 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) {
|
|
||||||
if !s.debug {
|
|
||||||
return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable")
|
|
||||||
}
|
|
||||||
if request == nil {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "missing debug crash request")
|
|
||||||
}
|
|
||||||
switch request.Type {
|
|
||||||
case DebugCrashRequest_GO:
|
|
||||||
time.AfterFunc(200*time.Millisecond, func() {
|
|
||||||
*(*int)(unsafe.Pointer(uintptr(0))) = 0
|
|
||||||
})
|
|
||||||
case DebugCrashRequest_NATIVE:
|
|
||||||
err := s.handler.TriggerNativeCrash()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "unknown debug crash type")
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error {
|
||||||
instance := s.Instance()
|
|
||||||
if instance == nil {
|
|
||||||
return nil, status.Error(codes.FailedPrecondition, "service not started")
|
|
||||||
}
|
|
||||||
reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx)
|
|
||||||
if reporter == nil {
|
|
||||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, reporter.WriteReport(memory.Total())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
err := s.waitForStarted(server.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -742,260 +674,69 @@ func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsReque
|
|||||||
s.serviceAccess.RLock()
|
s.serviceAccess.RLock()
|
||||||
boxService := s.instance
|
boxService := s.instance
|
||||||
s.serviceAccess.RUnlock()
|
s.serviceAccess.RUnlock()
|
||||||
|
ticker := time.NewTicker(time.Duration(request.Interval))
|
||||||
if boxService.clashServer == nil {
|
|
||||||
return E.New("clash server not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager()
|
|
||||||
|
|
||||||
subscription, done, err := s.connectionEventObserver.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.connectionEventObserver.UnSubscribe(subscription)
|
|
||||||
|
|
||||||
connectionSnapshots := make(map[uuid.UUID]connectionSnapshot)
|
|
||||||
initialEvents := s.buildInitialConnectionState(trafficManager, connectionSnapshots)
|
|
||||||
err = server.Send(&ConnectionEvents{
|
|
||||||
Events: initialEvents,
|
|
||||||
Reset_: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
interval := time.Duration(request.Interval)
|
|
||||||
if interval <= 0 {
|
|
||||||
interval = time.Second
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(interval)
|
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager()
|
||||||
|
var (
|
||||||
|
connections = make(map[uuid.UUID]*Connection)
|
||||||
|
outConnections []*Connection
|
||||||
|
)
|
||||||
for {
|
for {
|
||||||
|
outConnections = outConnections[:0]
|
||||||
|
for _, connection := range trafficManager.Connections() {
|
||||||
|
outConnections = append(outConnections, newConnection(connections, connection, false))
|
||||||
|
}
|
||||||
|
for _, connection := range trafficManager.ClosedConnections() {
|
||||||
|
outConnections = append(outConnections, newConnection(connections, connection, true))
|
||||||
|
}
|
||||||
|
err := server.Send(&Connections{Connections: outConnections})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-s.ctx.Done():
|
case <-s.ctx.Done():
|
||||||
return s.ctx.Err()
|
return s.ctx.Err()
|
||||||
case <-server.Context().Done():
|
case <-server.Context().Done():
|
||||||
return server.Context().Err()
|
return server.Context().Err()
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case event := <-subscription:
|
|
||||||
var pendingEvents []*ConnectionEvent
|
|
||||||
if protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil {
|
|
||||||
pendingEvents = append(pendingEvents, protoEvent)
|
|
||||||
}
|
|
||||||
drain:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event = <-subscription:
|
|
||||||
if protoEvent := s.applyConnectionEvent(event, connectionSnapshots); protoEvent != nil {
|
|
||||||
pendingEvents = append(pendingEvents, protoEvent)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break drain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(pendingEvents) > 0 {
|
|
||||||
err = server.Send(&ConnectionEvents{Events: pendingEvents})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
protoEvents := s.buildTrafficUpdates(trafficManager, connectionSnapshots)
|
|
||||||
if len(protoEvents) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = server.Send(&ConnectionEvents{Events: protoEvents})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectionSnapshot struct {
|
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection {
|
||||||
uplink int64
|
if oldConnection, loaded := connections[metadata.ID]; loaded {
|
||||||
downlink int64
|
if isClosed {
|
||||||
hadTraffic bool
|
if oldConnection.ClosedAt == 0 {
|
||||||
}
|
oldConnection.Uplink = 0
|
||||||
|
oldConnection.Downlink = 0
|
||||||
func (s *StartedService) buildInitialConnectionState(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent {
|
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
|
||||||
var events []*ConnectionEvent
|
|
||||||
|
|
||||||
for _, metadata := range manager.Connections() {
|
|
||||||
events = append(events, &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_NEW,
|
|
||||||
Id: metadata.ID.String(),
|
|
||||||
Connection: buildConnectionProto(metadata),
|
|
||||||
})
|
|
||||||
snapshots[metadata.ID] = connectionSnapshot{
|
|
||||||
uplink: metadata.Upload.Load(),
|
|
||||||
downlink: metadata.Download.Load(),
|
|
||||||
}
|
}
|
||||||
|
return oldConnection
|
||||||
}
|
}
|
||||||
|
lastUplink := oldConnection.UplinkTotal
|
||||||
for _, metadata := range manager.ClosedConnections() {
|
lastDownlink := oldConnection.DownlinkTotal
|
||||||
conn := buildConnectionProto(metadata)
|
uplinkTotal := metadata.Upload.Load()
|
||||||
conn.ClosedAt = metadata.ClosedAt.UnixMilli()
|
downlinkTotal := metadata.Download.Load()
|
||||||
events = append(events, &ConnectionEvent{
|
oldConnection.Uplink = uplinkTotal - lastUplink
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_NEW,
|
oldConnection.Downlink = downlinkTotal - lastDownlink
|
||||||
Id: metadata.ID.String(),
|
oldConnection.UplinkTotal = uplinkTotal
|
||||||
Connection: conn,
|
oldConnection.DownlinkTotal = downlinkTotal
|
||||||
})
|
return oldConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
return events
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) applyConnectionEvent(event trafficontrol.ConnectionEvent, snapshots map[uuid.UUID]connectionSnapshot) *ConnectionEvent {
|
|
||||||
switch event.Type {
|
|
||||||
case trafficontrol.ConnectionEventNew:
|
|
||||||
if _, exists := snapshots[event.ID]; exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
snapshots[event.ID] = connectionSnapshot{
|
|
||||||
uplink: event.Metadata.Upload.Load(),
|
|
||||||
downlink: event.Metadata.Download.Load(),
|
|
||||||
}
|
|
||||||
return &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_NEW,
|
|
||||||
Id: event.ID.String(),
|
|
||||||
Connection: buildConnectionProto(event.Metadata),
|
|
||||||
}
|
|
||||||
case trafficontrol.ConnectionEventClosed:
|
|
||||||
delete(snapshots, event.ID)
|
|
||||||
protoEvent := &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_CLOSED,
|
|
||||||
Id: event.ID.String(),
|
|
||||||
}
|
|
||||||
closedAt := event.ClosedAt
|
|
||||||
if closedAt.IsZero() && !event.Metadata.ClosedAt.IsZero() {
|
|
||||||
closedAt = event.Metadata.ClosedAt
|
|
||||||
}
|
|
||||||
if closedAt.IsZero() {
|
|
||||||
closedAt = time.Now()
|
|
||||||
}
|
|
||||||
protoEvent.ClosedAt = closedAt.UnixMilli()
|
|
||||||
if event.Metadata.ID != uuid.Nil {
|
|
||||||
conn := buildConnectionProto(event.Metadata)
|
|
||||||
conn.ClosedAt = protoEvent.ClosedAt
|
|
||||||
protoEvent.Connection = conn
|
|
||||||
}
|
|
||||||
return protoEvent
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) buildTrafficUpdates(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent {
|
|
||||||
activeConnections := manager.Connections()
|
|
||||||
activeIndex := make(map[uuid.UUID]*trafficontrol.TrackerMetadata, len(activeConnections))
|
|
||||||
var events []*ConnectionEvent
|
|
||||||
|
|
||||||
for _, metadata := range activeConnections {
|
|
||||||
activeIndex[metadata.ID] = metadata
|
|
||||||
currentUpload := metadata.Upload.Load()
|
|
||||||
currentDownload := metadata.Download.Load()
|
|
||||||
snapshot, exists := snapshots[metadata.ID]
|
|
||||||
if !exists {
|
|
||||||
snapshots[metadata.ID] = connectionSnapshot{
|
|
||||||
uplink: currentUpload,
|
|
||||||
downlink: currentDownload,
|
|
||||||
}
|
|
||||||
events = append(events, &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_NEW,
|
|
||||||
Id: metadata.ID.String(),
|
|
||||||
Connection: buildConnectionProto(metadata),
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
uplinkDelta := currentUpload - snapshot.uplink
|
|
||||||
downlinkDelta := currentDownload - snapshot.downlink
|
|
||||||
if uplinkDelta < 0 || downlinkDelta < 0 {
|
|
||||||
if snapshot.hadTraffic {
|
|
||||||
events = append(events, &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_UPDATE,
|
|
||||||
Id: metadata.ID.String(),
|
|
||||||
UplinkDelta: 0,
|
|
||||||
DownlinkDelta: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
snapshot.uplink = currentUpload
|
|
||||||
snapshot.downlink = currentDownload
|
|
||||||
snapshot.hadTraffic = false
|
|
||||||
snapshots[metadata.ID] = snapshot
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if uplinkDelta > 0 || downlinkDelta > 0 {
|
|
||||||
snapshot.uplink = currentUpload
|
|
||||||
snapshot.downlink = currentDownload
|
|
||||||
snapshot.hadTraffic = true
|
|
||||||
snapshots[metadata.ID] = snapshot
|
|
||||||
events = append(events, &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_UPDATE,
|
|
||||||
Id: metadata.ID.String(),
|
|
||||||
UplinkDelta: uplinkDelta,
|
|
||||||
DownlinkDelta: downlinkDelta,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if snapshot.hadTraffic {
|
|
||||||
snapshot.uplink = currentUpload
|
|
||||||
snapshot.downlink = currentDownload
|
|
||||||
snapshot.hadTraffic = false
|
|
||||||
snapshots[metadata.ID] = snapshot
|
|
||||||
events = append(events, &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_UPDATE,
|
|
||||||
Id: metadata.ID.String(),
|
|
||||||
UplinkDelta: 0,
|
|
||||||
DownlinkDelta: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var closedIndex map[uuid.UUID]*trafficontrol.TrackerMetadata
|
|
||||||
for id := range snapshots {
|
|
||||||
if _, exists := activeIndex[id]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if closedIndex == nil {
|
|
||||||
closedIndex = make(map[uuid.UUID]*trafficontrol.TrackerMetadata)
|
|
||||||
for _, metadata := range manager.ClosedConnections() {
|
|
||||||
closedIndex[metadata.ID] = metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedAt := time.Now()
|
|
||||||
var conn *Connection
|
|
||||||
if metadata, ok := closedIndex[id]; ok {
|
|
||||||
if !metadata.ClosedAt.IsZero() {
|
|
||||||
closedAt = metadata.ClosedAt
|
|
||||||
}
|
|
||||||
conn = buildConnectionProto(metadata)
|
|
||||||
conn.ClosedAt = closedAt.UnixMilli()
|
|
||||||
}
|
|
||||||
events = append(events, &ConnectionEvent{
|
|
||||||
Type: ConnectionEventType_CONNECTION_EVENT_CLOSED,
|
|
||||||
Id: id.String(),
|
|
||||||
ClosedAt: closedAt.UnixMilli(),
|
|
||||||
Connection: conn,
|
|
||||||
})
|
|
||||||
delete(snapshots, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return events
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {
|
|
||||||
var rule string
|
var rule string
|
||||||
if metadata.Rule != nil {
|
if metadata.Rule != nil {
|
||||||
rule = metadata.Rule.String()
|
rule = metadata.Rule.String()
|
||||||
}
|
}
|
||||||
uplinkTotal := metadata.Upload.Load()
|
uplinkTotal := metadata.Upload.Load()
|
||||||
downlinkTotal := metadata.Download.Load()
|
downlinkTotal := metadata.Download.Load()
|
||||||
|
uplink := uplinkTotal
|
||||||
|
downlink := downlinkTotal
|
||||||
|
var closedAt int64
|
||||||
|
if !metadata.ClosedAt.IsZero() {
|
||||||
|
closedAt = metadata.ClosedAt.UnixMilli()
|
||||||
|
uplink = 0
|
||||||
|
downlink = 0
|
||||||
|
}
|
||||||
var processInfo *ProcessInfo
|
var processInfo *ProcessInfo
|
||||||
if metadata.Metadata.ProcessInfo != nil {
|
if metadata.Metadata.ProcessInfo != nil {
|
||||||
processInfo = &ProcessInfo{
|
processInfo = &ProcessInfo{
|
||||||
@@ -1003,10 +744,10 @@ func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {
|
|||||||
UserId: metadata.Metadata.ProcessInfo.UserId,
|
UserId: metadata.Metadata.ProcessInfo.UserId,
|
||||||
UserName: metadata.Metadata.ProcessInfo.UserName,
|
UserName: metadata.Metadata.ProcessInfo.UserName,
|
||||||
ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath,
|
ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath,
|
||||||
PackageNames: metadata.Metadata.ProcessInfo.AndroidPackageNames,
|
PackageName: metadata.Metadata.ProcessInfo.AndroidPackageName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Connection{
|
connection := &Connection{
|
||||||
Id: metadata.ID.String(),
|
Id: metadata.ID.String(),
|
||||||
Inbound: metadata.Metadata.Inbound,
|
Inbound: metadata.Metadata.Inbound,
|
||||||
InboundType: metadata.Metadata.InboundType,
|
InboundType: metadata.Metadata.InboundType,
|
||||||
@@ -1019,6 +760,9 @@ func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {
|
|||||||
User: metadata.Metadata.User,
|
User: metadata.Metadata.User,
|
||||||
FromOutbound: metadata.Metadata.Outbound,
|
FromOutbound: metadata.Metadata.Outbound,
|
||||||
CreatedAt: metadata.CreatedAt.UnixMilli(),
|
CreatedAt: metadata.CreatedAt.UnixMilli(),
|
||||||
|
ClosedAt: closedAt,
|
||||||
|
Uplink: uplink,
|
||||||
|
Downlink: downlink,
|
||||||
UplinkTotal: uplinkTotal,
|
UplinkTotal: uplinkTotal,
|
||||||
DownlinkTotal: downlinkTotal,
|
DownlinkTotal: downlinkTotal,
|
||||||
Rule: rule,
|
Rule: rule,
|
||||||
@@ -1027,6 +771,8 @@ func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {
|
|||||||
ChainList: metadata.Chain,
|
ChainList: metadata.Chain,
|
||||||
ProcessInfo: processInfo,
|
ProcessInfo: processInfo,
|
||||||
}
|
}
|
||||||
|
connections[metadata.ID] = connection
|
||||||
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) {
|
func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) {
|
||||||
@@ -1047,12 +793,7 @@ func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
func (s *StartedService) CloseAllConnections(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
s.serviceAccess.RLock()
|
conntrack.Close()
|
||||||
nowService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
if nowService != nil && nowService.connectionManager != nil {
|
|
||||||
nowService.connectionManager.CloseAll()
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1071,9 +812,6 @@ func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *empty
|
|||||||
Message: it.Message(),
|
Message: it.Message(),
|
||||||
Impending: it.Impending(),
|
Impending: it.Impending(),
|
||||||
MigrationLink: it.MigrationLink,
|
MigrationLink: it.MigrationLink,
|
||||||
Description: it.Description,
|
|
||||||
DeprecatedVersion: it.DeprecatedVersion,
|
|
||||||
ScheduledVersion: it.ScheduledVersion,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1085,384 +823,12 @@ func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty)
|
|||||||
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeOutbounds(_ *emptypb.Empty, server grpc.ServerStreamingServer[OutboundList]) error {
|
func (s *StartedService) SubscribeHelperEvents(empty *emptypb.Empty, server grpc.ServerStreamingServer[HelperRequest]) error {
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
subscription, done, err := s.urlTestObserver.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.urlTestObserver.UnSubscribe(subscription)
|
|
||||||
for {
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
historyStorage := boxService.urlTestHistoryStorage
|
|
||||||
var list OutboundList
|
|
||||||
for _, ob := range boxService.instance.Outbound().Outbounds() {
|
|
||||||
item := &GroupItem{
|
|
||||||
Tag: ob.Tag(),
|
|
||||||
Type: ob.Type(),
|
|
||||||
}
|
|
||||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ob)); history != nil {
|
|
||||||
item.UrlTestTime = history.Time.Unix()
|
|
||||||
item.UrlTestDelay = int32(history.Delay)
|
|
||||||
}
|
|
||||||
list.Outbounds = append(list.Outbounds, item)
|
|
||||||
}
|
|
||||||
for _, ep := range boxService.instance.Endpoint().Endpoints() {
|
|
||||||
item := &GroupItem{
|
|
||||||
Tag: ep.Tag(),
|
|
||||||
Type: ep.Type(),
|
|
||||||
}
|
|
||||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ep)); history != nil {
|
|
||||||
item.UrlTestTime = history.Time.Unix()
|
|
||||||
item.UrlTestDelay = int32(history.Delay)
|
|
||||||
}
|
|
||||||
list.Outbounds = append(list.Outbounds, item)
|
|
||||||
}
|
|
||||||
err = server.Send(&list)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-subscription:
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
return s.ctx.Err()
|
|
||||||
case <-server.Context().Done():
|
|
||||||
return server.Context().Err()
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveOutbound(instance *Instance, tag string) (adapter.Outbound, error) {
|
func (s *StartedService) SendHelperResponse(ctx context.Context, response *HelperResponse) (*emptypb.Empty, error) {
|
||||||
if tag == "" {
|
return nil, os.ErrInvalid
|
||||||
return instance.instance.Outbound().Default(), nil
|
|
||||||
}
|
|
||||||
outbound, loaded := instance.instance.Outbound().Outbound(tag)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound not found: ", tag)
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartNetworkQualityTest(
|
|
||||||
request *NetworkQualityTestRequest,
|
|
||||||
server grpc.ServerStreamingServer[NetworkQualityTestProgress],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
outbound, err := resolveOutbound(boxService, request.OutboundTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
|
||||||
httpClient := networkquality.NewHTTPClient(resolvedDialer)
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
|
|
||||||
measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(resolvedDialer, request.Http3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, nqErr := networkquality.Run(networkquality.Options{
|
|
||||||
ConfigURL: request.ConfigURL,
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
NewMeasurementClient: measurementClientFactory,
|
|
||||||
Serial: request.Serial,
|
|
||||||
MaxRuntime: time.Duration(request.MaxRuntimeSeconds) * time.Second,
|
|
||||||
Context: server.Context(),
|
|
||||||
OnProgress: func(p networkquality.Progress) {
|
|
||||||
_ = server.Send(&NetworkQualityTestProgress{
|
|
||||||
Phase: int32(p.Phase),
|
|
||||||
DownloadCapacity: p.DownloadCapacity,
|
|
||||||
UploadCapacity: p.UploadCapacity,
|
|
||||||
DownloadRPM: p.DownloadRPM,
|
|
||||||
UploadRPM: p.UploadRPM,
|
|
||||||
IdleLatencyMs: p.IdleLatencyMs,
|
|
||||||
ElapsedMs: p.ElapsedMs,
|
|
||||||
DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy),
|
|
||||||
UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy),
|
|
||||||
DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy),
|
|
||||||
UploadRPMAccuracy: int32(p.UploadRPMAccuracy),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if nqErr != nil {
|
|
||||||
return server.Send(&NetworkQualityTestProgress{
|
|
||||||
IsFinal: true,
|
|
||||||
Error: nqErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return server.Send(&NetworkQualityTestProgress{
|
|
||||||
Phase: int32(networkquality.PhaseDone),
|
|
||||||
DownloadCapacity: result.DownloadCapacity,
|
|
||||||
UploadCapacity: result.UploadCapacity,
|
|
||||||
DownloadRPM: result.DownloadRPM,
|
|
||||||
UploadRPM: result.UploadRPM,
|
|
||||||
IdleLatencyMs: result.IdleLatencyMs,
|
|
||||||
IsFinal: true,
|
|
||||||
DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy),
|
|
||||||
UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy),
|
|
||||||
DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy),
|
|
||||||
UploadRPMAccuracy: int32(result.UploadRPMAccuracy),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartSTUNTest(
|
|
||||||
request *STUNTestRequest,
|
|
||||||
server grpc.ServerStreamingServer[STUNTestProgress],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
outbound, err := resolveOutbound(boxService, request.OutboundTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
|
||||||
|
|
||||||
result, stunErr := stun.Run(stun.Options{
|
|
||||||
Server: request.Server,
|
|
||||||
Dialer: resolvedDialer,
|
|
||||||
Context: server.Context(),
|
|
||||||
OnProgress: func(p stun.Progress) {
|
|
||||||
_ = server.Send(&STUNTestProgress{
|
|
||||||
Phase: int32(p.Phase),
|
|
||||||
ExternalAddr: p.ExternalAddr,
|
|
||||||
LatencyMs: p.LatencyMs,
|
|
||||||
NatMapping: int32(p.NATMapping),
|
|
||||||
NatFiltering: int32(p.NATFiltering),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if stunErr != nil {
|
|
||||||
return server.Send(&STUNTestProgress{
|
|
||||||
IsFinal: true,
|
|
||||||
Error: stunErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return server.Send(&STUNTestProgress{
|
|
||||||
Phase: int32(stun.PhaseDone),
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NatMapping: int32(result.NATMapping),
|
|
||||||
NatFiltering: int32(result.NATFiltering),
|
|
||||||
IsFinal: true,
|
|
||||||
NatTypeSupported: result.NATTypeSupported,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) SubscribeTailscaleStatus(
|
|
||||||
_ *emptypb.Empty,
|
|
||||||
server grpc.ServerStreamingServer[TailscaleStatusUpdate],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
type tailscaleEndpoint struct {
|
|
||||||
tag string
|
|
||||||
provider adapter.TailscaleEndpoint
|
|
||||||
}
|
|
||||||
var endpoints []tailscaleEndpoint
|
|
||||||
for _, endpoint := range endpointManager.Endpoints() {
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
provider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if !loaded {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
endpoints = append(endpoints, tailscaleEndpoint{
|
|
||||||
tag: endpoint.Tag(),
|
|
||||||
provider: provider,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(endpoints) == 0 {
|
|
||||||
return status.Error(codes.NotFound, "no Tailscale endpoint found")
|
|
||||||
}
|
|
||||||
|
|
||||||
type taggedStatus struct {
|
|
||||||
tag string
|
|
||||||
status *adapter.TailscaleEndpointStatus
|
|
||||||
}
|
|
||||||
updates := make(chan taggedStatus, len(endpoints))
|
|
||||||
ctx, cancel := context.WithCancel(server.Context())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var waitGroup sync.WaitGroup
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
waitGroup.Add(1)
|
|
||||||
go func(tag string, provider adapter.TailscaleEndpoint) {
|
|
||||||
defer waitGroup.Done()
|
|
||||||
_ = provider.SubscribeTailscaleStatus(ctx, func(endpointStatus *adapter.TailscaleEndpointStatus) {
|
|
||||||
select {
|
|
||||||
case updates <- taggedStatus{tag: tag, status: endpointStatus}:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}(endpoint.tag, endpoint.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
waitGroup.Wait()
|
|
||||||
close(updates)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var tags []string
|
|
||||||
statuses := make(map[string]*adapter.TailscaleEndpointStatus, len(endpoints))
|
|
||||||
for update := range updates {
|
|
||||||
if _, exists := statuses[update.tag]; !exists {
|
|
||||||
tags = append(tags, update.tag)
|
|
||||||
}
|
|
||||||
statuses[update.tag] = update.status
|
|
||||||
protoEndpoints := make([]*TailscaleEndpointStatus, 0, len(statuses))
|
|
||||||
for _, tag := range tags {
|
|
||||||
protoEndpoints = append(protoEndpoints, tailscaleEndpointStatusToProto(tag, statuses[tag]))
|
|
||||||
}
|
|
||||||
sendErr := server.Send(&TailscaleStatusUpdate{
|
|
||||||
Endpoints: protoEndpoints,
|
|
||||||
})
|
|
||||||
if sendErr != nil {
|
|
||||||
return sendErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStatus) *TailscaleEndpointStatus {
|
|
||||||
userGroups := make([]*TailscaleUserGroup, len(s.UserGroups))
|
|
||||||
for i, group := range s.UserGroups {
|
|
||||||
peers := make([]*TailscalePeer, len(group.Peers))
|
|
||||||
for j, peer := range group.Peers {
|
|
||||||
peers[j] = tailscalePeerToProto(peer)
|
|
||||||
}
|
|
||||||
userGroups[i] = &TailscaleUserGroup{
|
|
||||||
UserID: group.UserID,
|
|
||||||
LoginName: group.LoginName,
|
|
||||||
DisplayName: group.DisplayName,
|
|
||||||
ProfilePicURL: group.ProfilePicURL,
|
|
||||||
Peers: peers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := &TailscaleEndpointStatus{
|
|
||||||
EndpointTag: tag,
|
|
||||||
BackendState: s.BackendState,
|
|
||||||
AuthURL: s.AuthURL,
|
|
||||||
NetworkName: s.NetworkName,
|
|
||||||
MagicDNSSuffix: s.MagicDNSSuffix,
|
|
||||||
UserGroups: userGroups,
|
|
||||||
}
|
|
||||||
if s.Self != nil {
|
|
||||||
result.Self = tailscalePeerToProto(s.Self)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscalePeerToProto(peer *adapter.TailscalePeer) *TailscalePeer {
|
|
||||||
return &TailscalePeer{
|
|
||||||
HostName: peer.HostName,
|
|
||||||
DnsName: peer.DNSName,
|
|
||||||
Os: peer.OS,
|
|
||||||
TailscaleIPs: peer.TailscaleIPs,
|
|
||||||
Online: peer.Online,
|
|
||||||
ExitNode: peer.ExitNode,
|
|
||||||
ExitNodeOption: peer.ExitNodeOption,
|
|
||||||
Active: peer.Active,
|
|
||||||
RxBytes: peer.RxBytes,
|
|
||||||
TxBytes: peer.TxBytes,
|
|
||||||
KeyExpiry: peer.KeyExpiry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartTailscalePing(
|
|
||||||
request *TailscalePingRequest,
|
|
||||||
server grpc.ServerStreamingServer[TailscalePingResponse],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider adapter.TailscaleEndpoint
|
|
||||||
if request.EndpointTag != "" {
|
|
||||||
endpoint, loaded := endpointManager.Get(request.EndpointTag)
|
|
||||||
if !loaded {
|
|
||||||
return status.Error(codes.NotFound, "endpoint not found: "+request.EndpointTag)
|
|
||||||
}
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
return status.Error(codes.InvalidArgument, "endpoint is not Tailscale: "+request.EndpointTag)
|
|
||||||
}
|
|
||||||
pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if !loaded {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint does not support ping")
|
|
||||||
}
|
|
||||||
provider = pingProvider
|
|
||||||
} else {
|
|
||||||
for _, endpoint := range endpointManager.Endpoints() {
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if loaded {
|
|
||||||
provider = pingProvider
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if provider == nil {
|
|
||||||
return status.Error(codes.NotFound, "no Tailscale endpoint found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.StartTailscalePing(server.Context(), request.PeerIP, func(result *adapter.TailscalePingResult) {
|
|
||||||
_ = server.Send(&TailscalePingResponse{
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
IsDirect: result.IsDirect,
|
|
||||||
Endpoint: result.Endpoint,
|
|
||||||
DerpRegionID: result.DERPRegionID,
|
|
||||||
DerpRegionCode: result.DERPRegionCode,
|
|
||||||
Error: result.Error,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ package daemon;
|
|||||||
option go_package = "github.com/sagernet/sing-box/daemon";
|
option go_package = "github.com/sagernet/sing-box/daemon";
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
import "daemon/helper.proto";
|
||||||
|
|
||||||
service StartedService {
|
service StartedService {
|
||||||
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
rpc StopService(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||||
@@ -26,20 +27,15 @@ service StartedService {
|
|||||||
|
|
||||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||||
rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {}
|
|
||||||
rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
|
||||||
|
|
||||||
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {}
|
||||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||||
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||||
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||||
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
||||||
|
|
||||||
rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {}
|
rpc SubscribeHelperEvents(google.protobuf.Empty) returns(stream HelperRequest) {}
|
||||||
rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {}
|
rpc SendHelperResponse(HelperResponse) returns(google.protobuf.Empty) {}
|
||||||
rpc StartSTUNTest(STUNTestRequest) returns (stream STUNTestProgress) {}
|
|
||||||
rpc SubscribeTailscaleStatus(google.protobuf.Empty) returns (stream TailscaleStatusUpdate) {}
|
|
||||||
rpc StartTailscalePing(TailscalePingRequest) returns (stream TailscalePingResponse) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServiceStatus {
|
message ServiceStatus {
|
||||||
@@ -149,37 +145,26 @@ message SetSystemProxyEnabledRequest {
|
|||||||
bool enabled = 1;
|
bool enabled = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DebugCrashRequest {
|
|
||||||
enum Type {
|
|
||||||
GO = 0;
|
|
||||||
NATIVE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeConnectionsRequest {
|
message SubscribeConnectionsRequest {
|
||||||
int64 interval = 1;
|
int64 interval = 1;
|
||||||
|
ConnectionFilter filter = 2;
|
||||||
|
ConnectionSortBy sortBy = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ConnectionEventType {
|
enum ConnectionFilter {
|
||||||
CONNECTION_EVENT_NEW = 0;
|
ALL = 0;
|
||||||
CONNECTION_EVENT_UPDATE = 1;
|
ACTIVE = 1;
|
||||||
CONNECTION_EVENT_CLOSED = 2;
|
CLOSED = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ConnectionEvent {
|
enum ConnectionSortBy {
|
||||||
ConnectionEventType type = 1;
|
DATE = 0;
|
||||||
string id = 2;
|
TRAFFIC = 1;
|
||||||
Connection connection = 3;
|
TOTAL_TRAFFIC = 2;
|
||||||
int64 uplinkDelta = 4;
|
|
||||||
int64 downlinkDelta = 5;
|
|
||||||
int64 closedAt = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ConnectionEvents {
|
message Connections {
|
||||||
repeated ConnectionEvent events = 1;
|
repeated Connection connections = 1;
|
||||||
bool reset = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Connection {
|
message Connection {
|
||||||
@@ -212,7 +197,7 @@ message ProcessInfo {
|
|||||||
int32 userId = 2;
|
int32 userId = 2;
|
||||||
string userName = 3;
|
string userName = 3;
|
||||||
string processPath = 4;
|
string processPath = 4;
|
||||||
repeated string packageNames = 5;
|
string packageName = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CloseConnectionRequest {
|
message CloseConnectionRequest {
|
||||||
@@ -227,105 +212,8 @@ message DeprecatedWarning {
|
|||||||
string message = 1;
|
string message = 1;
|
||||||
bool impending = 2;
|
bool impending = 2;
|
||||||
string migrationLink = 3;
|
string migrationLink = 3;
|
||||||
string description = 4;
|
|
||||||
string deprecatedVersion = 5;
|
|
||||||
string scheduledVersion = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartedAt {
|
message StartedAt {
|
||||||
int64 startedAt = 1;
|
int64 startedAt = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message OutboundList {
|
|
||||||
repeated GroupItem outbounds = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkQualityTestRequest {
|
|
||||||
string configURL = 1;
|
|
||||||
string outboundTag = 2;
|
|
||||||
bool serial = 3;
|
|
||||||
int32 maxRuntimeSeconds = 4;
|
|
||||||
bool http3 = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkQualityTestProgress {
|
|
||||||
int32 phase = 1;
|
|
||||||
int64 downloadCapacity = 2;
|
|
||||||
int64 uploadCapacity = 3;
|
|
||||||
int32 downloadRPM = 4;
|
|
||||||
int32 uploadRPM = 5;
|
|
||||||
int32 idleLatencyMs = 6;
|
|
||||||
int64 elapsedMs = 7;
|
|
||||||
bool isFinal = 8;
|
|
||||||
string error = 9;
|
|
||||||
int32 downloadCapacityAccuracy = 10;
|
|
||||||
int32 uploadCapacityAccuracy = 11;
|
|
||||||
int32 downloadRPMAccuracy = 12;
|
|
||||||
int32 uploadRPMAccuracy = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
message STUNTestRequest {
|
|
||||||
string server = 1;
|
|
||||||
string outboundTag = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message STUNTestProgress {
|
|
||||||
int32 phase = 1;
|
|
||||||
string externalAddr = 2;
|
|
||||||
int32 latencyMs = 3;
|
|
||||||
int32 natMapping = 4;
|
|
||||||
int32 natFiltering = 5;
|
|
||||||
bool isFinal = 6;
|
|
||||||
string error = 7;
|
|
||||||
bool natTypeSupported = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleStatusUpdate {
|
|
||||||
repeated TailscaleEndpointStatus endpoints = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleEndpointStatus {
|
|
||||||
string endpointTag = 1;
|
|
||||||
string backendState = 2;
|
|
||||||
string authURL = 3;
|
|
||||||
string networkName = 4;
|
|
||||||
string magicDNSSuffix = 5;
|
|
||||||
TailscalePeer self = 6;
|
|
||||||
repeated TailscaleUserGroup userGroups = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleUserGroup {
|
|
||||||
int64 userID = 1;
|
|
||||||
string loginName = 2;
|
|
||||||
string displayName = 3;
|
|
||||||
string profilePicURL = 4;
|
|
||||||
repeated TailscalePeer peers = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePeer {
|
|
||||||
string hostName = 1;
|
|
||||||
string dnsName = 2;
|
|
||||||
string os = 3;
|
|
||||||
repeated string tailscaleIPs = 4;
|
|
||||||
bool online = 5;
|
|
||||||
bool exitNode = 6;
|
|
||||||
bool exitNodeOption = 7;
|
|
||||||
bool active = 8;
|
|
||||||
int64 rxBytes = 9;
|
|
||||||
int64 txBytes = 10;
|
|
||||||
int64 keyExpiry = 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePingRequest {
|
|
||||||
string endpointTag = 1;
|
|
||||||
string peerIP = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePingResponse {
|
|
||||||
double latencyMs = 1;
|
|
||||||
bool isDirect = 2;
|
|
||||||
string endpoint = 3;
|
|
||||||
int32 derpRegionID = 4;
|
|
||||||
string derpRegionCode = 5;
|
|
||||||
string error = 6;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,18 +31,13 @@ const (
|
|||||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||||
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
|
|
||||||
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
|
|
||||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||||
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
|
StartedService_SubscribeHelperEvents_FullMethodName = "/daemon.StartedService/SubscribeHelperEvents"
|
||||||
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
|
StartedService_SendHelperResponse_FullMethodName = "/daemon.StartedService/SendHelperResponse"
|
||||||
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
|
|
||||||
StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus"
|
|
||||||
StartedService_StartTailscalePing_FullMethodName = "/daemon.StartedService/StartTailscalePing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartedServiceClient is the client API for StartedService service.
|
// StartedServiceClient is the client API for StartedService service.
|
||||||
@@ -65,18 +60,13 @@ type StartedServiceClient interface {
|
|||||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error)
|
||||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
|
||||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
||||||
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
||||||
SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error)
|
SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error)
|
||||||
StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error)
|
SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error)
|
|
||||||
SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error)
|
|
||||||
StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type startedServiceClient struct {
|
type startedServiceClient struct {
|
||||||
@@ -292,33 +282,13 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *startedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, StartedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *startedServiceClient) TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, ConnectionEvents]{ClientStream: stream}
|
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{ClientStream: stream}
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -329,7 +299,7 @@ func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *Sub
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[ConnectionEvents]
|
type StartedService_SubscribeConnectionsClient = grpc.ServerStreamingClient[Connections]
|
||||||
|
|
||||||
func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
func (c *startedServiceClient) CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
@@ -371,13 +341,13 @@ func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Emp
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error) {
|
func (c *startedServiceClient) SubscribeHelperEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelperRequest], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeOutbounds_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeHelperEvents_FullMethodName, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
x := &grpc.GenericClientStream[emptypb.Empty, OutboundList]{ClientStream: stream}
|
x := &grpc.GenericClientStream[emptypb.Empty, HelperRequest]{ClientStream: stream}
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -388,84 +358,18 @@ func (c *startedServiceClient) SubscribeOutbounds(ctx context.Context, in *empty
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type StartedService_SubscribeOutboundsClient = grpc.ServerStreamingClient[OutboundList]
|
type StartedService_SubscribeHelperEventsClient = grpc.ServerStreamingClient[HelperRequest]
|
||||||
|
|
||||||
func (c *startedServiceClient) StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error) {
|
func (c *startedServiceClient) SendHelperResponse(ctx context.Context, in *HelperResponse, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[7], StartedService_StartNetworkQualityTest_FullMethodName, cOpts...)
|
out := new(emptypb.Empty)
|
||||||
|
err := c.cc.Invoke(ctx, StartedService_SendHelperResponse_FullMethodName, in, out, cOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
x := &grpc.GenericClientStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ClientStream: stream}
|
return out, nil
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartNetworkQualityTestClient = grpc.ServerStreamingClient[NetworkQualityTestProgress]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[8], StartedService_StartSTUNTest_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[STUNTestRequest, STUNTestProgress]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartSTUNTestClient = grpc.ServerStreamingClient[STUNTestProgress]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[9], StartedService_SubscribeTailscaleStatus_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[emptypb.Empty, TailscaleStatusUpdate]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeTailscaleStatusClient = grpc.ServerStreamingClient[TailscaleStatusUpdate]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[10], StartedService_StartTailscalePing_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[TailscalePingRequest, TailscalePingResponse]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartTailscalePingClient = grpc.ServerStreamingClient[TailscalePingResponse]
|
|
||||||
|
|
||||||
// StartedServiceServer is the server API for StartedService service.
|
// StartedServiceServer is the server API for StartedService service.
|
||||||
// All implementations must embed UnimplementedStartedServiceServer
|
// All implementations must embed UnimplementedStartedServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -486,18 +390,13 @@ type StartedServiceServer interface {
|
|||||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||||
TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error)
|
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error
|
||||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
|
||||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
|
||||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
||||||
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
||||||
SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error
|
SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error
|
||||||
StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error
|
SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error)
|
||||||
StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error
|
|
||||||
SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error
|
|
||||||
StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error
|
|
||||||
mustEmbedUnimplementedStartedServiceServer()
|
mustEmbedUnimplementedStartedServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,115 +408,95 @@ type StartedServiceServer interface {
|
|||||||
type UnimplementedStartedServiceServer struct{}
|
type UnimplementedStartedServiceServer struct{}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) StopService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method StopService not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method StopService not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) ReloadService(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method ReloadService not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ReloadService not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error {
|
func (UnimplementedStartedServiceServer) SubscribeServiceStatus(*emptypb.Empty, grpc.ServerStreamingServer[ServiceStatus]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeServiceStatus not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeServiceStatus not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error {
|
func (UnimplementedStartedServiceServer) SubscribeLog(*emptypb.Empty, grpc.ServerStreamingServer[Log]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeLog not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeLog not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) {
|
func (UnimplementedStartedServiceServer) GetDefaultLogLevel(context.Context, *emptypb.Empty) (*DefaultLogLevel, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetDefaultLogLevel not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetDefaultLogLevel not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) ClearLogs(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method ClearLogs not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ClearLogs not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {
|
func (UnimplementedStartedServiceServer) SubscribeStatus(*SubscribeStatusRequest, grpc.ServerStreamingServer[Status]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeStatus not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeStatus not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error {
|
func (UnimplementedStartedServiceServer) SubscribeGroups(*emptypb.Empty, grpc.ServerStreamingServer[Groups]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeGroups not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeGroups not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) {
|
func (UnimplementedStartedServiceServer) GetClashModeStatus(context.Context, *emptypb.Empty) (*ClashModeStatus, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetClashModeStatus not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetClashModeStatus not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error {
|
func (UnimplementedStartedServiceServer) SubscribeClashMode(*emptypb.Empty, grpc.ServerStreamingServer[ClashMode]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeClashMode not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeClashMode not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) SetClashMode(context.Context, *ClashMode) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method SetClashMode not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SetClashMode not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) URLTest(context.Context, *URLTestRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method URLTest not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method URLTest not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) SelectOutbound(context.Context, *SelectOutboundRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method SelectOutbound not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SelectOutbound not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method SetGroupExpand not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SetGroupExpand not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {
|
func (UnimplementedStartedServiceServer) GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetSystemProxyStatus not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetSystemProxyStatus not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error {
|
||||||
return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method CloseConnection not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method CloseAllConnections not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {
|
func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetStartedAt not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error {
|
func (UnimplementedStartedServiceServer) SubscribeHelperEvents(*emptypb.Empty, grpc.ServerStreamingServer[HelperRequest]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeOutbounds not implemented")
|
return status.Errorf(codes.Unimplemented, "method SubscribeHelperEvents not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error {
|
func (UnimplementedStartedServiceServer) SendHelperResponse(context.Context, *HelperResponse) (*emptypb.Empty, error) {
|
||||||
return status.Error(codes.Unimplemented, "method StartNetworkQualityTest not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SendHelperResponse not implemented")
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartSTUNTest not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeTailscaleStatus not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartTailscalePing not implemented")
|
|
||||||
}
|
}
|
||||||
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||||
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||||
@@ -630,7 +509,7 @@ type UnsafeStartedServiceServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) {
|
func RegisterStartedServiceServer(s grpc.ServiceRegistrar, srv StartedServiceServer) {
|
||||||
// If the following call panics, it indicates UnimplementedStartedServiceServer was
|
// If the following call pancis, it indicates UnimplementedStartedServiceServer was
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
@@ -893,52 +772,16 @@ func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _StartedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(DebugCrashRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: StartedService_TriggerDebugCrash_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _StartedService_TriggerOOMReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(emptypb.Empty)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: StartedService_TriggerOOMReport_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(SubscribeConnectionsRequest)
|
m := new(SubscribeConnectionsRequest)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, ConnectionEvents]{ServerStream: stream})
|
return srv.(StartedServiceServer).SubscribeConnections(m, &grpc.GenericServerStream[SubscribeConnectionsRequest, Connections]{ServerStream: stream})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[ConnectionEvents]
|
type StartedService_SubscribeConnectionsServer = grpc.ServerStreamingServer[Connections]
|
||||||
|
|
||||||
func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _StartedService_CloseConnection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(CloseConnectionRequest)
|
in := new(CloseConnectionRequest)
|
||||||
@@ -1012,61 +855,35 @@ func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context,
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _StartedService_SubscribeOutbounds_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _StartedService_SubscribeHelperEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(emptypb.Empty)
|
m := new(emptypb.Empty)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return srv.(StartedServiceServer).SubscribeOutbounds(m, &grpc.GenericServerStream[emptypb.Empty, OutboundList]{ServerStream: stream})
|
return srv.(StartedServiceServer).SubscribeHelperEvents(m, &grpc.GenericServerStream[emptypb.Empty, HelperRequest]{ServerStream: stream})
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
type StartedService_SubscribeOutboundsServer = grpc.ServerStreamingServer[OutboundList]
|
type StartedService_SubscribeHelperEventsServer = grpc.ServerStreamingServer[HelperRequest]
|
||||||
|
|
||||||
func _StartedService_StartNetworkQualityTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _StartedService_SendHelperResponse_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
m := new(NetworkQualityTestRequest)
|
in := new(HelperResponse)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := dec(in); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
return srv.(StartedServiceServer).StartNetworkQualityTest(m, &grpc.GenericServerStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ServerStream: stream})
|
if interceptor == nil {
|
||||||
}
|
return srv.(StartedServiceServer).SendHelperResponse(ctx, in)
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartNetworkQualityTestServer = grpc.ServerStreamingServer[NetworkQualityTestProgress]
|
|
||||||
|
|
||||||
func _StartedService_StartSTUNTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(STUNTestRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return srv.(StartedServiceServer).StartSTUNTest(m, &grpc.GenericServerStream[STUNTestRequest, STUNTestProgress]{ServerStream: stream})
|
info := &grpc.UnaryServerInfo{
|
||||||
}
|
Server: srv,
|
||||||
|
FullMethod: StartedService_SendHelperResponse_FullMethodName,
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartSTUNTestServer = grpc.ServerStreamingServer[STUNTestProgress]
|
|
||||||
|
|
||||||
func _StartedService_SubscribeTailscaleStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(emptypb.Empty)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return srv.(StartedServiceServer).SubscribeTailscaleStatus(m, &grpc.GenericServerStream[emptypb.Empty, TailscaleStatusUpdate]{ServerStream: stream})
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
}
|
return srv.(StartedServiceServer).SendHelperResponse(ctx, req.(*HelperResponse))
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeTailscaleStatusServer = grpc.ServerStreamingServer[TailscaleStatusUpdate]
|
|
||||||
|
|
||||||
func _StartedService_StartTailscalePing_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(TailscalePingRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return srv.(StartedServiceServer).StartTailscalePing(m, &grpc.GenericServerStream[TailscalePingRequest, TailscalePingResponse]{ServerStream: stream})
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartTailscalePingServer = grpc.ServerStreamingServer[TailscalePingResponse]
|
|
||||||
|
|
||||||
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -1118,14 +935,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SetSystemProxyEnabled",
|
MethodName: "SetSystemProxyEnabled",
|
||||||
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "TriggerDebugCrash",
|
|
||||||
Handler: _StartedService_TriggerDebugCrash_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "TriggerOOMReport",
|
|
||||||
Handler: _StartedService_TriggerOOMReport_Handler,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
MethodName: "CloseConnection",
|
MethodName: "CloseConnection",
|
||||||
Handler: _StartedService_CloseConnection_Handler,
|
Handler: _StartedService_CloseConnection_Handler,
|
||||||
@@ -1142,6 +951,10 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetStartedAt",
|
MethodName: "GetStartedAt",
|
||||||
Handler: _StartedService_GetStartedAt_Handler,
|
Handler: _StartedService_GetStartedAt_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SendHelperResponse",
|
||||||
|
Handler: _StartedService_SendHelperResponse_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
@@ -1175,28 +988,8 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
StreamName: "SubscribeOutbounds",
|
StreamName: "SubscribeHelperEvents",
|
||||||
Handler: _StartedService_SubscribeOutbounds_Handler,
|
Handler: _StartedService_SubscribeHelperEvents_Handler,
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartNetworkQualityTest",
|
|
||||||
Handler: _StartedService_StartNetworkQualityTest_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartSTUNTest",
|
|
||||||
Handler: _StartedService_StartSTUNTest_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "SubscribeTailscaleStatus",
|
|
||||||
Handler: _StartedService_SubscribeTailscaleStatus_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartTailscalePing",
|
|
||||||
Handler: _StartedService_StartTailscalePing_Handler,
|
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
8
debug.go
8
debug.go
@@ -3,11 +3,11 @@ package box
|
|||||||
import (
|
import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) error {
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
applyDebugListenOption(options)
|
applyDebugListenOption(options)
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
@@ -26,9 +26,9 @@ func applyDebugOptions(options option.DebugOptions) error {
|
|||||||
}
|
}
|
||||||
if options.MemoryLimit.Value() != 0 {
|
if options.MemoryLimit.Value() != 0 {
|
||||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||||
|
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
||||||
}
|
}
|
||||||
if options.OOMKiller != nil {
|
if options.OOMKiller != nil {
|
||||||
return E.New("legacy oom_killer in debug options is removed, use oom-killer service instead")
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
524
dns/client.go
524
dns/client.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"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"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
"github.com/sagernet/sing/contrab/freelru"
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
@@ -30,63 +32,59 @@ var (
|
|||||||
var _ adapter.DNSClient = (*Client)(nil)
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ctx context.Context
|
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
optimisticTimeout time.Duration
|
independentCache bool
|
||||||
cacheCapacity uint32
|
|
||||||
clientSubnet netip.Prefix
|
clientSubnet netip.Prefix
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
dnsCache adapter.DNSCacheStore
|
|
||||||
initDNSCacheFunc func() adapter.DNSCacheStore
|
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
cache freelru.Cache[dnsCacheKey, *dns.Msg]
|
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||||
cacheLock compatible.Map[dnsCacheKey, chan struct{}]
|
cacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
backgroundRefresh compatible.Map[dnsCacheKey, struct{}]
|
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||||
|
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
Context context.Context
|
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
DisableExpire bool
|
DisableExpire bool
|
||||||
OptimisticTimeout time.Duration
|
IndependentCache bool
|
||||||
CacheCapacity uint32
|
CacheCapacity uint32
|
||||||
ClientSubnet netip.Prefix
|
ClientSubnet netip.Prefix
|
||||||
RDRC func() adapter.RDRCStore
|
RDRC func() adapter.RDRCStore
|
||||||
DNSCache func() adapter.DNSCacheStore
|
|
||||||
Logger logger.ContextLogger
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(options ClientOptions) *Client {
|
func NewClient(options ClientOptions) *Client {
|
||||||
cacheCapacity := options.CacheCapacity
|
|
||||||
if cacheCapacity < 1024 {
|
|
||||||
cacheCapacity = 1024
|
|
||||||
}
|
|
||||||
client := &Client{
|
client := &Client{
|
||||||
ctx: options.Context,
|
|
||||||
timeout: options.Timeout,
|
timeout: options.Timeout,
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
disableExpire: options.DisableExpire,
|
disableExpire: options.DisableExpire,
|
||||||
optimisticTimeout: options.OptimisticTimeout,
|
independentCache: options.IndependentCache,
|
||||||
cacheCapacity: cacheCapacity,
|
|
||||||
clientSubnet: options.ClientSubnet,
|
clientSubnet: options.ClientSubnet,
|
||||||
initRDRCFunc: options.RDRC,
|
initRDRCFunc: options.RDRC,
|
||||||
initDNSCacheFunc: options.DNSCache,
|
|
||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
if client.timeout == 0 {
|
if client.timeout == 0 {
|
||||||
client.timeout = C.DNSTimeout
|
client.timeout = C.DNSTimeout
|
||||||
}
|
}
|
||||||
if !client.disableCache && client.initDNSCacheFunc == nil {
|
cacheCapacity := options.CacheCapacity
|
||||||
client.initializeMemoryCache()
|
if cacheCapacity < 1024 {
|
||||||
|
cacheCapacity = 1024
|
||||||
|
}
|
||||||
|
if !client.disableCache {
|
||||||
|
if !client.independentCache {
|
||||||
|
client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32))
|
||||||
|
} else {
|
||||||
|
client.transportCache = common.Must1(freelru.NewSharded[transportCacheKey, *dns.Msg](cacheCapacity, maphash.NewHasher[transportCacheKey]().Hash32))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
type dnsCacheKey struct {
|
type transportCacheKey struct {
|
||||||
dns.Question
|
dns.Question
|
||||||
transportTag string
|
transportTag string
|
||||||
}
|
}
|
||||||
@@ -95,19 +93,6 @@ func (c *Client) Start() {
|
|||||||
if c.initRDRCFunc != nil {
|
if c.initRDRCFunc != nil {
|
||||||
c.rdrc = c.initRDRCFunc()
|
c.rdrc = c.initRDRCFunc()
|
||||||
}
|
}
|
||||||
if c.initDNSCacheFunc != nil {
|
|
||||||
c.dnsCache = c.initDNSCacheFunc()
|
|
||||||
}
|
|
||||||
if c.dnsCache == nil {
|
|
||||||
c.initializeMemoryCache()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) initializeMemoryCache() {
|
|
||||||
if c.disableCache || c.cache != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.cache = common.Must1(freelru.NewSharded[dnsCacheKey, *dns.Msg](c.cacheCapacity, maphash.NewHasher[dnsCacheKey]().Hash32))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
||||||
@@ -124,38 +109,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeTimeToLive(response *dns.Msg) uint32 {
|
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||||
var timeToLive uint32
|
|
||||||
if len(response.Answer) == 0 {
|
|
||||||
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
|
||||||
return soaTTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
|
||||||
timeToLive = record.Header().Ttl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return timeToLive
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeTTL(response *dns.Msg, timeToLive uint32) {
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = timeToLive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) {
|
|
||||||
if len(message.Question) == 0 {
|
if len(message.Question) == 0 {
|
||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
@@ -169,7 +123,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
||||||
}
|
}
|
||||||
message = c.prepareExchangeMessage(message, options)
|
clientSubnet := options.ClientSubnet
|
||||||
|
if !clientSubnet.IsValid() {
|
||||||
|
clientSubnet = c.clientSubnet
|
||||||
|
}
|
||||||
|
if clientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
|
}
|
||||||
|
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -181,34 +141,34 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
!options.ClientSubnet.IsValid()
|
!options.ClientSubnet.IsValid()
|
||||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
cacheKey := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
if c.cache != nil {
|
||||||
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
||||||
if loaded {
|
if loaded {
|
||||||
select {
|
<-cond
|
||||||
case <-cond:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
c.cacheLock.Delete(cacheKey)
|
c.cacheLock.Delete(question)
|
||||||
close(cond)
|
close(cond)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
response, ttl, isStale := c.loadResponse(question, transport)
|
} else if c.transportCache != nil {
|
||||||
|
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
|
||||||
|
if loaded {
|
||||||
|
<-cond
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
c.transportCacheLock.Delete(question)
|
||||||
|
close(cond)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response, ttl := c.loadResponse(question, transport)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
if isStale && !options.DisableOptimisticCache {
|
|
||||||
c.backgroundRefreshDNS(transport, question, message.Copy(), options, responseChecker)
|
|
||||||
logOptimisticResponse(c.logger, ctx, response)
|
|
||||||
response.Id = message.Id
|
|
||||||
return response, nil
|
|
||||||
} else if !isStale {
|
|
||||||
logCachedResponse(c.logger, ctx, response, ttl)
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
response.Id = message.Id
|
response.Id = message.Id
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
messageId := message.Id
|
messageId := message.Id
|
||||||
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
||||||
@@ -222,17 +182,60 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return nil, ErrResponseRejectedCached
|
return nil, ErrResponseRejectedCached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response, err := c.exchangeToTransport(ctx, transport, message)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
|
response, err := transport.Exchange(ctx, message)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
var rcodeError RcodeError
|
||||||
|
if errors.As(err, &rcodeError) {
|
||||||
|
response = FixedResponseStatus(message, int(rcodeError))
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||||
|
validResponse := response
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
addresses int
|
||||||
|
queryCNAME string
|
||||||
|
)
|
||||||
|
for _, rawRR := range validResponse.Answer {
|
||||||
|
switch rr := rawRR.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
break loop
|
||||||
|
case *dns.AAAA:
|
||||||
|
break loop
|
||||||
|
case *dns.CNAME:
|
||||||
|
queryCNAME = rr.Target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if queryCNAME == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Question = []dns.Question{{
|
||||||
|
Name: queryCNAME,
|
||||||
|
Qtype: question.Qtype,
|
||||||
|
}}
|
||||||
|
validResponse, err = c.Exchange(ctx, transport, &exMessage, options, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if validResponse != response {
|
||||||
|
response.Answer = append(response.Answer, validResponse.Answer...)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
// TODO: add accept_any rule and support to check response instead of addresses
|
||||||
|
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
|
||||||
rejected = true
|
rejected = true
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(response)
|
rejected = !responseChecker(MessageToAddresses(response))
|
||||||
}
|
}
|
||||||
if rejected {
|
if rejected {
|
||||||
if !disableCache && c.rdrc != nil {
|
if !disableCache && c.rdrc != nil {
|
||||||
@@ -242,7 +245,48 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, ErrResponseRejected
|
return response, ErrResponseRejected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeToLive := applyResponseOptions(question, response, options)
|
if question.Qtype == dns.TypeHTTPS {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only {
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
https, isHTTPS := rr.(*dns.HTTPS)
|
||||||
|
if !isHTTPS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content := https.SVCB
|
||||||
|
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
|
return it.Key() != dns.SVCB_IPV6HINT
|
||||||
|
} else {
|
||||||
|
return it.Key() != dns.SVCB_IPV4HINT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
https.SVCB = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var timeToLive uint32
|
||||||
|
if len(response.Answer) == 0 {
|
||||||
|
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
||||||
|
timeToLive = soaTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timeToLive == 0 {
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||||
|
timeToLive = record.Header().Ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.RewriteTTL != nil {
|
||||||
|
timeToLive = *options.RewriteTTL
|
||||||
|
}
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = timeToLive
|
||||||
|
}
|
||||||
|
}
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
}
|
}
|
||||||
@@ -261,7 +305,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
domain = FqdnToDomain(domain)
|
domain = FqdnToDomain(domain)
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
var strategy C.DomainStrategy
|
var strategy C.DomainStrategy
|
||||||
@@ -270,20 +314,16 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
} else {
|
} else {
|
||||||
strategy = options.Strategy
|
strategy = options.Strategy
|
||||||
}
|
}
|
||||||
lookupOptions := options
|
|
||||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
|
||||||
lookupOptions.Strategy = strategy
|
|
||||||
}
|
|
||||||
if strategy == C.DomainStrategyIPv4Only {
|
if strategy == C.DomainStrategyIPv4Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
}
|
}
|
||||||
var response4 []netip.Addr
|
var response4 []netip.Addr
|
||||||
var response6 []netip.Addr
|
var response6 []netip.Addr
|
||||||
var group task.Group
|
var group task.Group
|
||||||
group.Append("exchange4", func(ctx context.Context) error {
|
group.Append("exchange4", func(ctx context.Context) error {
|
||||||
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, lookupOptions, responseChecker)
|
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -291,7 +331,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
group.Append("exchange6", func(ctx context.Context) error {
|
group.Append("exchange6", func(ctx context.Context) error {
|
||||||
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, lookupOptions, responseChecker)
|
response, err := c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -308,12 +348,8 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
if c.cache != nil {
|
if c.cache != nil {
|
||||||
c.cache.Purge()
|
c.cache.Purge()
|
||||||
}
|
} else if c.transportCache != nil {
|
||||||
if c.dnsCache != nil {
|
c.transportCache.Purge()
|
||||||
err := c.dnsCache.ClearDNSCache()
|
|
||||||
if err != nil && c.logger != nil {
|
|
||||||
c.logger.Warn("clear DNS cache: ", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,44 +365,46 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
if timeToLive == 0 {
|
if timeToLive == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.dnsCache != nil {
|
|
||||||
packed, err := message.Pack()
|
|
||||||
if err == nil {
|
|
||||||
expireAt := time.Now().Add(time.Second * time.Duration(timeToLive))
|
|
||||||
c.dnsCache.SaveDNSCacheAsync(transport.Tag(), question.Name, question.Qtype, packed, expireAt, c.logger)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.cache == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
if c.disableExpire {
|
if c.disableExpire {
|
||||||
c.cache.Add(key, message.Copy())
|
if !c.independentCache {
|
||||||
|
c.cache.Add(question, message)
|
||||||
} else {
|
} else {
|
||||||
c.cache.AddWithLifetime(key, message.Copy(), time.Second*time.Duration(timeToLive))
|
c.transportCache.Add(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !c.independentCache {
|
||||||
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
|
} else {
|
||||||
|
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, message, time.Second*time.Duration(timeToLive))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
question := dns.Question{
|
question := dns.Question{
|
||||||
Name: name,
|
Name: name,
|
||||||
Qtype: qType,
|
Qtype: qType,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}
|
}
|
||||||
|
disableCache := c.disableCache || options.DisableCache
|
||||||
|
if !disableCache {
|
||||||
|
cachedAddresses, err := c.questionCache(question, transport)
|
||||||
|
if err != ErrNotCached {
|
||||||
|
return cachedAddresses, err
|
||||||
|
}
|
||||||
|
}
|
||||||
message := dns.Msg{
|
message := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
disableCache := c.disableCache || options.DisableCache
|
|
||||||
if !disableCache {
|
|
||||||
cachedAddresses, err := c.questionCache(ctx, transport, &message, options, responseChecker)
|
|
||||||
if err != ErrNotCached {
|
|
||||||
return cachedAddresses, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -377,181 +415,111 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
|
|||||||
return MessageToAddresses(response), nil
|
return MessageToAddresses(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) questionCache(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
||||||
question := message.Question[0]
|
response, _ := c.loadResponse(question, transport)
|
||||||
response, _, isStale := c.loadResponse(question, transport)
|
|
||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, ErrNotCached
|
return nil, ErrNotCached
|
||||||
}
|
}
|
||||||
if isStale {
|
|
||||||
if options.DisableOptimisticCache {
|
|
||||||
return nil, ErrNotCached
|
|
||||||
}
|
|
||||||
c.backgroundRefreshDNS(transport, question, c.prepareExchangeMessage(message.Copy(), options), options, responseChecker)
|
|
||||||
logOptimisticResponse(c.logger, ctx, response)
|
|
||||||
}
|
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
if response.Rcode != dns.RcodeSuccess {
|
||||||
return nil, RcodeError(response.Rcode)
|
return nil, RcodeError(response.Rcode)
|
||||||
}
|
}
|
||||||
return MessageToAddresses(response), nil
|
return MessageToAddresses(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) {
|
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
||||||
if c.dnsCache != nil {
|
var (
|
||||||
return c.loadPersistentResponse(question, transport)
|
response *dns.Msg
|
||||||
}
|
loaded bool
|
||||||
if c.cache == nil {
|
)
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
if c.disableExpire {
|
if c.disableExpire {
|
||||||
response, loaded := c.cache.Get(key)
|
if !c.independentCache {
|
||||||
if !loaded {
|
response, loaded = c.cache.Get(question)
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
return response.Copy(), 0, false
|
|
||||||
}
|
|
||||||
response, expireAt, loaded := c.cache.GetWithLifetimeNoExpire(key)
|
|
||||||
if !loaded {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
timeNow := time.Now()
|
|
||||||
if timeNow.After(expireAt) {
|
|
||||||
if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) {
|
|
||||||
response = response.Copy()
|
|
||||||
normalizeTTL(response, 1)
|
|
||||||
return response, 0, true
|
|
||||||
}
|
|
||||||
c.cache.Remove(key)
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
|
||||||
if nowTTL < 0 {
|
|
||||||
nowTTL = 0
|
|
||||||
}
|
|
||||||
response = response.Copy()
|
|
||||||
normalizeTTL(response, uint32(nowTTL))
|
|
||||||
return response, nowTTL, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) loadPersistentResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) {
|
|
||||||
rawMessage, expireAt, loaded := c.dnsCache.LoadDNSCache(transport.Tag(), question.Name, question.Qtype)
|
|
||||||
if !loaded {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
response := new(dns.Msg)
|
|
||||||
err := response.Unpack(rawMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
if c.disableExpire {
|
|
||||||
return response, 0, false
|
|
||||||
}
|
|
||||||
timeNow := time.Now()
|
|
||||||
if timeNow.After(expireAt) {
|
|
||||||
if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) {
|
|
||||||
normalizeTTL(response, 1)
|
|
||||||
return response, 0, true
|
|
||||||
}
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
|
||||||
if nowTTL < 0 {
|
|
||||||
nowTTL = 0
|
|
||||||
}
|
|
||||||
normalizeTTL(response, uint32(nowTTL))
|
|
||||||
return response, nowTTL, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyResponseOptions(question dns.Question, response *dns.Msg, options adapter.DNSQueryOptions) uint32 {
|
|
||||||
if question.Qtype == dns.TypeHTTPS && (options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only) {
|
|
||||||
for _, rr := range response.Answer {
|
|
||||||
https, isHTTPS := rr.(*dns.HTTPS)
|
|
||||||
if !isHTTPS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content := https.SVCB
|
|
||||||
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
|
||||||
if options.Strategy == C.DomainStrategyIPv4Only {
|
|
||||||
return it.Key() != dns.SVCB_IPV6HINT
|
|
||||||
}
|
|
||||||
return it.Key() != dns.SVCB_IPV4HINT
|
|
||||||
})
|
|
||||||
https.SVCB = content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timeToLive := computeTimeToLive(response)
|
|
||||||
if options.RewriteTTL != nil {
|
|
||||||
timeToLive = *options.RewriteTTL
|
|
||||||
}
|
|
||||||
normalizeTTL(response, timeToLive)
|
|
||||||
return timeToLive
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) {
|
|
||||||
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
_, loaded := c.backgroundRefresh.LoadOrStore(key, struct{}{})
|
|
||||||
if loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
defer c.backgroundRefresh.Delete(key)
|
|
||||||
ctx := contextWithTransportTag(c.ctx, transport.Tag())
|
|
||||||
response, err := c.exchangeToTransport(ctx, transport, message)
|
|
||||||
if err != nil {
|
|
||||||
if c.logger != nil {
|
|
||||||
c.logger.Debug("optimistic refresh failed for ", FqdnToDomain(question.Name), ": ", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if responseChecker != nil {
|
|
||||||
var rejected bool
|
|
||||||
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
|
||||||
rejected = true
|
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(response)
|
response, loaded = c.transportCache.Get(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if rejected {
|
if !loaded {
|
||||||
if c.rdrc != nil {
|
return nil, 0
|
||||||
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
|
||||||
}
|
}
|
||||||
return
|
return response.Copy(), 0
|
||||||
|
} else {
|
||||||
|
var expireAt time.Time
|
||||||
|
if !c.independentCache {
|
||||||
|
response, expireAt, loaded = c.cache.GetWithLifetime(question)
|
||||||
|
} else {
|
||||||
|
response, expireAt, loaded = c.transportCache.GetWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
if !loaded {
|
||||||
return
|
return nil, 0
|
||||||
}
|
}
|
||||||
timeToLive := applyResponseOptions(question, response, options)
|
timeNow := time.Now()
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
if timeNow.After(expireAt) {
|
||||||
}()
|
if !c.independentCache {
|
||||||
}
|
c.cache.Remove(question)
|
||||||
|
} else {
|
||||||
func (c *Client) prepareExchangeMessage(message *dns.Msg, options adapter.DNSQueryOptions) *dns.Msg {
|
c.transportCache.Remove(transportCacheKey{
|
||||||
clientSubnet := options.ClientSubnet
|
Question: question,
|
||||||
if !clientSubnet.IsValid() {
|
transportTag: transport.Tag(),
|
||||||
clientSubnet = c.clientSubnet
|
})
|
||||||
}
|
}
|
||||||
if clientSubnet.IsValid() {
|
return nil, 0
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
|
||||||
}
|
}
|
||||||
return message
|
var originTTL int
|
||||||
}
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
func (c *Client) exchangeToTransport(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg) (*dns.Msg, error) {
|
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
originTTL = int(record.Header().Ttl)
|
||||||
defer cancel()
|
|
||||||
response, err := transport.Exchange(ctx, message)
|
|
||||||
if err == nil {
|
|
||||||
return response, nil
|
|
||||||
}
|
}
|
||||||
var rcodeError RcodeError
|
|
||||||
if errors.As(err, &rcodeError) {
|
|
||||||
return FixedResponseStatus(message, int(rcodeError)), nil
|
|
||||||
}
|
}
|
||||||
return nil, err
|
}
|
||||||
|
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
||||||
|
if nowTTL < 0 {
|
||||||
|
nowTTL = 0
|
||||||
|
}
|
||||||
|
response = response.Copy()
|
||||||
|
if originTTL > 0 {
|
||||||
|
duration := uint32(originTTL - nowTTL)
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = record.Header().Ttl - duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = uint32(nowTTL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response, nowTTL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||||
return adapter.DNSResponseAddresses(response)
|
if response == nil || response.Rcode != dns.RcodeSuccess {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
|
for _, rawAnswer := range response.Answer {
|
||||||
|
switch answer := rawAnswer.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.A))
|
||||||
|
case *dns.AAAA:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.AAAA))
|
||||||
|
case *dns.HTTPS:
|
||||||
|
for _, value := range answer.SVCB.Value {
|
||||||
|
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
|
||||||
|
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(err error) error {
|
func wrapError(err error) error {
|
||||||
|
|||||||
@@ -22,19 +22,6 @@ func logCachedResponse(logger logger.ContextLogger, ctx context.Context, respons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logOptimisticResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) {
|
|
||||||
if logger == nil || len(response.Question) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
domain := FqdnToDomain(response.Question[0].Name)
|
|
||||||
logger.DebugContext(ctx, "optimistic ", domain, " ", dns.RcodeToString[response.Rcode])
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
logger.InfoContext(ctx, "optimistic ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
||||||
if logger == nil || len(response.Question) == 0 {
|
if logger == nil || len(response.Question) == 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user