Compare commits

..

96 Commits

Author SHA1 Message Date
世界
1ee0b39817 documentation: Bump version 2026-01-07 18:46:28 +08:00
世界
06f202f0e3 tailscale: Add system interface support 2026-01-07 18:46:28 +08:00
世界
a8f8dcaedc platform: Fix gomobile build 2026-01-07 15:17:15 +08:00
世界
e7074732e7 platform: Update apple build comamnds 2026-01-07 15:17:14 +08:00
世界
ff101b57db platform: Improve interface 2026-01-07 15:17:14 +08:00
世界
762b567693 documentation: Bump version 2026-01-07 15:16:51 +08:00
世界
1a511ae711 Downgrade quic-go to v0.57.1 2026-01-07 15:16:51 +08:00
世界
dcf5fde029 Fix linux musl builds 2026-01-07 15:16:51 +08:00
世界
04949914f1 Fix openwrt builds 2026-01-07 15:16:51 +08:00
世界
fbc0fe43ad Add kmod-nft-queue dependency for openwrt package 2026-01-07 15:16:51 +08:00
世界
857319ef3d Fix nfqueue fallback 2026-01-07 15:16:51 +08:00
世界
b48ddee8b1 Disable multipath TCP by default via GODEBUG 2026-01-07 15:16:51 +08:00
世界
8e3d70d197 Fix missing relay support for Tailscale 2026-01-07 15:16:51 +08:00
世界
21468cda93 cronet: Fix windows DNS hijack 2026-01-07 15:16:51 +08:00
世界
2ff67c9ece Fix DNS transports 2026-01-07 15:16:51 +08:00
世界
6ff279ba49 platform: Expose process info 2026-01-07 15:16:51 +08:00
世界
5d15a241e3 Update bypass action behavior for auto redirect 2026-01-07 15:16:51 +08:00
世界
e01a5c96a5 platform: Add GetStartedAt for StartedService 2026-01-07 15:16:51 +08:00
世界
9d40074cfa documentation: Format changes header 2026-01-07 15:16:51 +08:00
世界
4796bce0c2 Add format_docs command for documentation trailing space formatting 2026-01-07 15:16:51 +08:00
世界
fe632ef3ff Fix panic when closing Box before Start with file log output 2026-01-07 15:16:51 +08:00
世界
b15fecf992 Add pre-match support for auto redirect 2026-01-07 15:16:51 +08:00
世界
774269a2bf Fix cronet on iOS 2026-01-07 15:16:51 +08:00
世界
c995273244 Ignore darwin IP_DONTFRAG error when not supported 2026-01-07 15:15:29 +08:00
世界
a80208e7b1 Update tailscale to v1.92.4 2026-01-07 15:15:29 +08:00
世界
60580df446 Update cronet-go to v143.0.7499.109-1 2026-01-07 15:15:29 +08:00
世界
d28305be5f platform: Split library for Android SDK 21 and 23 2026-01-07 15:15:29 +08:00
世界
886a45a204 Fix missing RootPoolFromContext and TimeFuncFromContext in HTTP clients 2026-01-07 15:15:29 +08:00
世界
401d195944 documentation: Minor fixes 2026-01-07 15:15:29 +08:00
世界
d7489c0653 documentation: Add Wi-Fi state shared page 2026-01-07 15:15:29 +08:00
世界
1d6d829dee Fix missing build constraints for linux wifi state monitor 2026-01-07 15:15:28 +08:00
世界
69555fc6ea Update dependencies 2026-01-07 15:15:28 +08:00
世界
655e534ea8 Update quic-go to v0.58.0 2026-01-07 15:15:28 +08:00
世界
5d86f63906 Add Chrome Root Store certificate option
Adds `chrome` as a new certificate store option alongside `mozilla`.
Both stores filter out China-based CA certificates.
2026-01-07 15:15:28 +08:00
世界
2162dab4a1 Fix cronet-go crash 2026-01-07 15:15:28 +08:00
世界
51fd4b7e1a Add trace logging for lifecycle calls
Log start/close operations with timing information for debugging.
2026-01-07 15:15:28 +08:00
世界
ac51a74dbe documentation: Minor fixes 2026-01-07 15:15:28 +08:00
世界
d8a15ecddf Remove certificate_public_key_sha256 for naive 2026-01-07 15:15:28 +08:00
世界
dda91d50bd platform: Use new crash log api 2026-01-07 15:15:28 +08:00
世界
b3aec2997b Fix naive network 2026-01-07 15:15:27 +08:00
世界
f3ae84442a Add QUIC support for naiveproxy 2026-01-07 15:15:27 +08:00
世界
8d373fb004 Add ECH support for NaiveProxy outbound and tls.ech.query_server_name option
- Enable ECH for NaiveProxy outbound with DNS resolver integration
- Add query_server_name option to override domain for ECH HTTPS record queries
- Update cronet-go dependency and remove windows_386 support
2026-01-07 15:15:27 +08:00
世界
db3c3a6621 Fix naiveproxy build 2026-01-07 15:15:27 +08:00
世界
eaaaddb61e Add OpenAI Codex Multiplexer service 2026-01-07 15:15:27 +08:00
世界
e97784d05b Update pricing for CCM service 2026-01-07 15:15:27 +08:00
世界
7a0d211224 release: Upload only other apks 2026-01-07 15:15:26 +08:00
世界
5264802927 Fix bugs and add UoT option for naiveproxy outbound 2026-01-07 15:15:26 +08:00
世界
69896f8b80 Add naiveproxy outbound 2026-01-07 15:15:26 +08:00
世界
0e3552da6f Apply ping destination filter for Windows 2026-01-07 15:15:26 +08:00
世界
0ed29288a0 platform: Add UsePlatformWIFIMonitor to gRPC interface
Align dev-next-grpc with wip2 by adding UsePlatformWIFIMonitor()
to the new PlatformInterface, allowing platform clients to indicate
they handle WIFI monitoring themselves.
2026-01-07 15:15:26 +08:00
世界
774fed206b daemon: Add clear logs 2026-01-07 15:15:26 +08:00
世界
71e1eb1e7f Revert "Stop using DHCP on iOS and tvOS" 2026-01-07 15:15:25 +08:00
世界
5112488d39 platform: Refactoring libbox to use gRPC-based protocol 2026-01-07 15:15:25 +08:00
世界
f228bd5b49 Add Windows WI-FI state support 2026-01-07 15:15:25 +08:00
世界
6424476ace Add Linux WI-FI state support
Support monitoring WIFI state on Linux through:
- NetworkManager (D-Bus)
- IWD (D-Bus)
- wpa_supplicant (control socket)
- ConnMan (D-Bus)
2026-01-07 15:15:24 +08:00
世界
51dc7c4f88 Add more tcp keep alive options
Also update default TCP keep-alive initial period from 10 minutes to 5 minutes.
2026-01-07 15:15:24 +08:00
世界
92c072d849 Update quic-go to v0.57.1 2026-01-07 15:15:24 +08:00
世界
ac7f0c9a3d Fix read credentials for ccm service 2026-01-07 15:15:24 +08:00
世界
31dcdc4f14 Add claude code multiplexer service 2026-01-07 15:15:23 +08:00
世界
3a267e1557 Fix compatibility with MPTCP 2026-01-07 15:15:23 +08:00
世界
73c5575866 Use a more conservative strategy for resolving with systemd-resolved for local DNS server 2026-01-07 15:15:23 +08:00
世界
f00854dd72 Fix missing mTLS support in client options 2026-01-07 15:15:23 +08:00
世界
c04d1414dd Add curve preferences, pinned public key SHA256 and mTLS for TLS options 2026-01-07 15:15:23 +08:00
世界
ec4e411fa7 Fix WireGuard input packet 2026-01-07 15:15:23 +08:00
世界
cc3a4c7dec Update tfo-go to latest 2026-01-07 15:15:22 +08:00
世界
20b0e4c3a0 Remove compatibility codes 2026-01-07 15:15:22 +08:00
世界
5aabc42823 Do not use linkname by default to simplify debugging 2026-01-07 15:15:22 +08:00
世界
f0771fb623 documentation: Update chinese translations 2026-01-07 15:15:22 +08:00
世界
67c79ebac8 Update quic-go to v0.55.0 2026-01-07 15:15:22 +08:00
世界
ef185fed09 Update WireGuard and Tailscale 2026-01-07 15:15:22 +08:00
世界
f8cdc40d62 Fix preConnectionCopy 2026-01-07 15:15:22 +08:00
世界
3bcf072750 Fix ping domain 2026-01-07 15:15:22 +08:00
世界
3ef15fa8aa release: Fix linux build 2026-01-07 15:15:21 +08:00
世界
159418324a Improve ktls rx error handling 2026-01-07 15:15:21 +08:00
世界
0c30128c44 Improve compatibility for kTLS 2026-01-07 15:15:21 +08:00
世界
354330cd80 ktls: Add warning for inappropriate scenarios 2026-01-07 15:15:21 +08:00
世界
b991ae1c91 Add support for kTLS
Reference: https://gitlab.com/go-extension/tls
2026-01-07 15:15:21 +08:00
世界
53fef79ab3 Add proxy support for ICMP echo request 2026-01-07 15:15:21 +08:00
世界
dfe2e083d5 Fix resolve using resolved 2026-01-07 15:15:21 +08:00
世界
ce408b2989 documentation: Update behavior of local DNS server on darwin 2026-01-07 15:15:21 +08:00
世界
395a1aadfa Remove use of ldflags -checklinkname=0 on darwin 2026-01-07 15:15:20 +08:00
世界
905573e386 Fix legacy DNS config 2026-01-07 15:15:20 +08:00
世界
5814753603 Fix rule-set format 2026-01-07 15:15:20 +08:00
世界
8cef967847 documentation: Remove outdated icons 2026-01-07 15:15:20 +08:00
世界
af532b8d05 documentation: Improve local DNS server 2026-01-07 15:15:20 +08:00
世界
5585855367 Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2026-01-07 15:15:20 +08:00
世界
2344b35b0f Improve local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2026-01-07 15:15:20 +08:00
世界
3a9ae0f91f Use resolved in local DNS server if available 2026-01-07 15:15:20 +08:00
xchacha20-poly1305
4fdbf786e1 Fix rule set version 2026-01-07 15:15:19 +08:00
世界
1e3ea29602 documentation: Add preferred_by route rule item 2026-01-07 15:15:19 +08:00
世界
ac5cb50368 Add preferred_by route rule item 2026-01-07 15:15:19 +08:00
世界
722f9ee971 documentation: Add interface address rule items 2026-01-07 15:15:19 +08:00
世界
3df72d04e9 Add interface address rule items 2026-01-07 15:15:19 +08:00
世界
4f93a515d6 Fix ECH retry support 2026-01-07 15:15:19 +08:00
neletor
a3c9ceca60 Add support for ech retry configs 2026-01-07 15:15:19 +08:00
Zephyruso
a57bdc82e8 Add /dns/flush-clash meta api 2026-01-07 15:15:18 +08:00
274 changed files with 3548 additions and 11202 deletions

View File

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

View File

@@ -4,7 +4,6 @@
--license GPL-3.0-or-later
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--vendor SagerNet
--maintainer "nekohasekai <contact-git@sekai.icu>"
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes

View File

@@ -1 +1 @@
2fef65f9dba90ddb89a87d00a6eb6165487c10c1
92d4602aba0ab6084673af0fe4887dccbc1049a5

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
":disableRateLimiting"
],
"baseBranches": [
"unstable"
"dev-next"
],
"golang": {
"enabled": false

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.25.8"
PATCH_COMMITS=(
"afe69d3cec1c6dcf0f1797b20546795730850070"
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
)
CURL_ARGS=(
-fL
--silent
--show-error
)
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
mkdir -p "$HOME/go"
cd "$HOME/go"
wget "https://dl.google.com/go/go${VERSION}.darwin-arm64.tar.gz"
tar -xzf "go${VERSION}.darwin-arm64.tar.gz"
#cp -a go go_bootstrap
mv go go_osx
cd go_osx
# these patch URLs only work on golang1.25.x
# that means after golang1.26 release it must be changed
# see: https://github.com/SagerNet/go/commits/release-branch.go1.25/
# revert:
# 33d3f603c1: "cmd/link/internal/ld: use 12.0.0 OS/SDK versions for macOS linking"
# 937368f84e: "crypto/x509: change how we retrieve chains on darwin"
for patch_commit in "${PATCH_COMMITS[@]}"; do
curl "${CURL_ARGS[@]}" "https://github.com/SagerNet/go/commit/${patch_commit}.diff" | patch --verbose -p 1
done
# Rebuild is not needed: we build with CGO_ENABLED=1, so Apple's external
# linker handles LC_BUILD_VERSION via MACOSX_DEPLOYMENT_TARGET, and the
# stdlib (crypto/x509) is compiled from patched src automatically.
#cd src
#GOROOT_BOOTSTRAP="$HOME/go/go_bootstrap" ./make.bash
#cd ../..
#rm -rf go_bootstrap "go${VERSION}.darwin-arm64.tar.gz"

View File

@@ -1,35 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.25.5"
VERSION="1.25.8"
PATCH_COMMITS=(
"466f6c7a29bc098b0d4c987b803c779222894a11"
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
"a90777dcf692dd2168577853ba743b4338721b06"
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
)
CURL_ARGS=(
-fL
--silent
--show-error
)
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
CURL_ARGS+=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
fi
mkdir -p "$HOME/go"
cd "$HOME/go"
mkdir -p $HOME/go
cd $HOME/go
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go go_win7
cd go_win7
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# these patch URLs only work on golang1.25.x
# this patch file only works on golang1.25.x
# that means after golang1.26 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
# revert:
@@ -37,10 +18,10 @@ cd go_win7
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
# fixes:
# bed309eff415bcb3c77dd4bc3277b682b89a388d: "Fix os.RemoveAll not working on Windows7"
# 34b899c2fb39b092db4fa67c4417e41dc046be4b: "Revert \"os: remove 5ms sleep on Windows in (*Process).Wait\""
for patch_commit in "${PATCH_COMMITS[@]}"; do
curl "${CURL_ARGS[@]}" "https://github.com/MetaCubeX/go/commit/${patch_commit}.diff" | patch --verbose -p 1
done
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
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

View File

@@ -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@$(git -C $PROJECTS/cronet-go rev-parse origin/go)
go mod tidy
git -C $PROJECTS/cronet-go rev-parse origin/go > "$SCRIPT_DIR/CRONET_GO_VERSION"
git -C $PROJECTS/cronet-go rev-parse origin/HEAD > "$SCRIPT_DIR/CRONET_GO_VERSION"

View File

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

View File

@@ -25,9 +25,8 @@ on:
- publish-android
push:
branches:
- stable
- testing
- unstable
- main-next
- dev-next
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
@@ -47,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.5
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -72,41 +71,33 @@ jobs:
include:
- { os: linux, arch: amd64, variant: purego, 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: 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", 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, variant: glibc, naive: true, goarm: "7" }
- { os: linux, arch: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, alpine: armv7, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
- { os: linux, arch: riscv64, naive: true, variant: glibc }
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: loong64, naive: true, variant: glibc }
- { os: linux, arch: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, alpine: loongarch64, openwrt: "loongarch64_generic" }
- { os: linux, arch: 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: "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: "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: mipsle, gomips: hardfloat, openwrt: "mipsel_24kc_24kf" }
- { os: linux, arch: mipsle, gomips: softfloat }
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
- { 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: 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: s390x, debian: s390x, rpm: s390x }
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
- { os: linux, arch: riscv64 }
- { os: linux, arch: loong64 }
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
- { 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: "386", legacy_win7: true, legacy_name: "windows-7" }
@@ -121,10 +112,15 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go
if: ${{ ! matrix.legacy_win7 }}
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
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
if: matrix.legacy_win7
id: cache-go-for-windows7
@@ -132,11 +128,9 @@ jobs:
with:
path: |
~/go/go_win7
key: go_win7_1258
key: go_win7_1255
- name: Setup Go for Windows 7
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |-
.github/setup_go_for_windows7.sh
- 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 checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Regenerate Debian keyring
if: matrix.naive
run: |
set -xeuo pipefail
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
cd ~/cronet-go
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/
~/cronet-go/naiveproxy/src/gn/out/
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
~/cronet-go/naiveproxy/src/out/sysroot-build/
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
~/cronet-go/naiveproxy/src/out/sysroot-build
key: chromium-toolchain-${{ matrix.arch }}-${{ matrix.variant }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
@@ -205,10 +190,9 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
TAGS="${TAGS},with_naive_outbound"
fi
if [[ "${{ matrix.variant }}" == "purego" ]]; then
TAGS="${TAGS},with_purego"
@@ -216,16 +200,13 @@ jobs:
TAGS="${TAGS},with_musl"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Set shared ldflags
run: |
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
- name: Build (purego)
if: matrix.variant == 'purego'
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
@@ -247,7 +228,7 @@ jobs:
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
@@ -255,8 +236,6 @@ jobs:
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (musl)
if: matrix.variant == 'musl'
@@ -264,7 +243,7 @@ jobs:
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
@@ -272,8 +251,6 @@ jobs:
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (non-variant)
if: matrix.os != 'android' && matrix.variant == ''
@@ -281,7 +258,7 @@ jobs:
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
@@ -301,7 +278,7 @@ jobs:
export CXX="${CC}++"
mkdir -p dist
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
@@ -375,7 +352,7 @@ jobs:
sudo gem install fpm
sudo apt-get update
sudo apt-get install -y libarchive-tools
cp .fpm_pacman .fpm
cp .fpm_systemd .fpm
fpm -t pacman \
-v "$PKG_VERSION" \
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
@@ -396,30 +373,6 @@ jobs:
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
done
rm "dist/openwrt.deb"
- name: Install apk-tools
if: matrix.openwrt != '' || matrix.alpine != ''
run: |-
docker run --rm -v /usr/local/bin:/mnt alpine:edge sh -c "apk add --no-cache apk-tools-static && cp /sbin/apk.static /mnt/apk && chmod +x /mnt/apk"
- name: Package OpenWrt APK
if: matrix.openwrt != ''
run: |-
set -xeuo pipefail
for architecture in ${{ matrix.openwrt }}; do
.github/build_openwrt_apk.sh \
"$architecture" \
"${{ needs.calculate_version.outputs.version }}" \
"dist/sing-box" \
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.apk"
done
- name: Package Alpine APK
if: matrix.alpine != ''
run: |-
set -xeuo pipefail
.github/build_alpine_apk.sh \
"${{ matrix.alpine }}" \
"${{ needs.calculate_version.outputs.version }}" \
"dist/sing-box" \
"dist/sing-box_${{ needs.calculate_version.outputs.version }}_linux_${{ matrix.alpine }}.apk"
- name: Archive
run: |
set -xeuo pipefail
@@ -455,36 +408,22 @@ jobs:
include:
- { arch: amd64 }
- { arch: arm64 }
- { arch: amd64, legacy_osx: true, legacy_name: "macos-10.13" }
- { arch: amd64, legacy_go124: true, legacy_name: "macos-11" }
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
if: ${{ ! matrix.legacy_osx }}
if: ${{ ! matrix.legacy_go124 }}
uses: actions/setup-go@v5
with:
go-version: ^1.25.3
- name: Cache Go for macOS 10.13
if: matrix.legacy_osx
id: cache-go-for-macos1013
uses: actions/cache@v4
- name: Setup Go 1.24
if: matrix.legacy_go124
uses: actions/setup-go@v5
with:
path: |
~/go/go_osx
key: go_osx_1258
- name: Setup Go for macOS 10.13
if: matrix.legacy_osx && steps.cache-go-for-macos1013.outputs.cache-hit != 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |-
.github/setup_go_for_macos1013.sh
- name: Setup Go for macOS 10.13
if: matrix.legacy_osx
run: |-
echo "PATH=$HOME/go/go_osx/bin:$PATH" >> $GITHUB_ENV
echo "GOROOT=$HOME/go/go_osx" >> $GITHUB_ENV
go-version: ~1.24.6
- name: Set tag
run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV"
@@ -492,27 +431,22 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
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.legacy_go124 }}" != "true" ]]; then
TAGS="${TAGS},with_naive_outbound"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Set shared ldflags
run: |
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
- name: Build
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: darwin
GOARCH: ${{ matrix.arch }}
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.legacy_osx && '10.13' || '' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set name
run: |-
@@ -565,11 +499,9 @@ jobs:
- name: Build
if: matrix.naive
run: |
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_WINDOWS
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
mkdir -p dist
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
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 "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" `
./cmd/sing-box
env:
CGO_ENABLED: "0"
@@ -579,11 +511,9 @@ jobs:
- name: Build
if: ${{ !matrix.naive }}
run: |
$TAGS = Get-Content release/DEFAULT_BUILD_TAGS_OTHERS
$LDFLAGS_SHARED = Get-Content release/LDFLAGS
mkdir -p dist
go build -v -trimpath -o dist/sing-box.exe -tags "$TAGS" `
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' $LDFLAGS_SHARED -s -w -buildid=" `
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 "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0" `
./cmd/sing-box
env:
CGO_ENABLED: "0"
@@ -628,7 +558,7 @@ jobs:
path: "dist"
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
needs:
- calculate_version
@@ -641,7 +571,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.5
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -664,12 +594,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- 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: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/testing'
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
@@ -718,7 +648,7 @@ jobs:
path: 'dist'
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
needs:
- calculate_version
@@ -731,7 +661,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.5
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -754,12 +684,12 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
- 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: |-
cd clients/android
git checkout main
- name: Checkout dev branch
if: github.ref == 'refs/heads/testing'
if: github.ref == 'refs/heads/dev-next'
run: |-
cd clients/android
git checkout dev
@@ -830,7 +760,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.5
- name: Set tag
if: matrix.if
run: |-
@@ -838,12 +768,12 @@ jobs:
git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- 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: |-
cd clients/apple
git checkout main
- name: Checkout dev branch
if: matrix.if && github.ref == 'refs/heads/testing'
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
cd clients/apple
git checkout dev
@@ -929,7 +859,7 @@ jobs:
-authenticationKeyID $ASC_KEY_ID \
-authenticationKeyIssuerID $ASC_KEY_ISSUER_ID
- 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: |-
go run -v ./cmd/internal/app_store_connect publish_testflight ${{ matrix.platform }}
- name: Build image

View File

@@ -3,8 +3,8 @@ name: Publish Docker Images
on:
#push:
# branches:
# - stable
# - testing
# - main-next
# - dev-next
release:
types:
- published
@@ -29,12 +29,10 @@ jobs:
- { arch: arm64, naive: true, docker_platform: "linux/arm64" }
- { arch: "386", naive: true, docker_platform: "linux/386" }
- { 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
- { arch: arm, goarm: "6", docker_platform: "linux/arm/v6" }
- { arch: ppc64le, docker_platform: "linux/ppc64le" }
- { arch: riscv64, docker_platform: "linux/riscv64" }
- { arch: s390x, docker_platform: "linux/s390x" }
steps:
- name: Get commit to build
@@ -55,7 +53,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.4
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -66,23 +64,14 @@ jobs:
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Regenerate Debian keyring
if: matrix.naive
run: |
set -xeuo pipefail
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
cd ~/cronet-go
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/
~/cronet-go/naiveproxy/src/gn/out/
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
~/cronet-go/naiveproxy/src/out/sysroot-build/
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
~/cronet-go/naiveproxy/src/out/sysroot-build
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
@@ -104,34 +93,29 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
TAGS="${TAGS},with_naive_outbound,with_musl"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Set shared ldflags
run: |
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
- name: Build (naive)
if: matrix.naive
run: |
set -xeuo pipefail
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
- name: Build (non-naive)
if: ${{ ! matrix.naive }}
run: |
set -xeuo pipefail
go build -v -trimpath -o sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=${VERSION}\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
env:
CGO_ENABLED: "0"
@@ -164,17 +148,15 @@ jobs:
strategy:
fail-fast: true
matrix:
include:
- { platform: "linux/amd64" }
- { platform: "linux/arm/v6" }
- { platform: "linux/arm/v7" }
- { platform: "linux/arm64" }
- { platform: "linux/386" }
# mipsle: no base Docker image available for this platform
- { platform: "linux/ppc64le" }
- { platform: "linux/riscv64" }
- { platform: "linux/s390x" }
- { platform: "linux/loong64", base_image: "ghcr.io/loong64/alpine:edge" }
platform:
- linux/amd64
- linux/arm/v6
- linux/arm/v7
- linux/arm64
- linux/386
- linux/ppc64le
- linux/riscv64
- linux/s390x
steps:
- name: Get commit to build
id: ref
@@ -227,8 +209,6 @@ jobs:
platforms: ${{ matrix.platform }}
context: .
file: Dockerfile.binary
build-args: |
BASE_IMAGE=${{ matrix.base_image || 'alpine' }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
@@ -244,7 +224,6 @@ jobs:
if-no-files-found: error
retention-days: 1
merge:
if: github.event_name != 'push'
runs-on: ubuntu-latest
needs:
- build_docker
@@ -259,13 +238,13 @@ jobs:
fi
echo "ref=$ref"
echo "ref=$ref" >> $GITHUB_OUTPUT
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
ref: ${{ steps.ref.outputs.ref }}
fetch-depth: 0
- name: Detect track
run: bash .github/detect_track.sh
if [[ $ref == *"-"* ]]; then
latest=latest-beta
else
latest=latest
fi
echo "latest=$latest"
echo "latest=$latest" >> $GITHUB_OUTPUT
- name: Download digests
uses: actions/download-artifact@v5
with:
@@ -285,11 +264,11 @@ jobs:
working-directory: /tmp/digests
run: |
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 }}" \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
- name: Inspect image
if: github.event_name != 'push'
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 }}

View File

@@ -3,20 +3,18 @@ name: Lint
on:
push:
branches:
- oldstable
- stable
- testing
- unstable
- stable-next
- main-next
- dev-next
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/lint.yml'
pull_request:
branches:
- oldstable
- stable
- testing
- unstable
- stable-next
- main-next
- dev-next
jobs:
build:

View File

@@ -3,14 +3,19 @@ name: Build Linux Packages
on:
#push:
# branches:
# - stable
# - testing
# - main-next
# - dev-next
workflow_dispatch:
inputs:
version:
description: "Version name"
required: true
type: string
forceBeta:
description: "Force beta"
required: false
type: boolean
default: false
release:
types:
- published
@@ -29,7 +34,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.5
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -56,14 +61,14 @@ jobs:
- { os: linux, arch: arm64, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64 }
- { os: linux, arch: "386", naive: true, debian: i386, rpm: i386 }
- { os: linux, arch: arm, goarm: "7", naive: true, debian: armhf, rpm: armv7hl, pacman: armv7hl }
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, debian: mipsel, rpm: mipsel }
- { os: linux, arch: riscv64, naive: true, debian: riscv64, rpm: riscv64 }
- { os: linux, arch: loong64, naive: true, debian: loongarch64, rpm: loongarch64 }
# Non-naive builds (unsupported architectures)
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
- { 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:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
@@ -72,7 +77,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.8
go-version: ^1.25.5
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -83,23 +88,14 @@ jobs:
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C ~/cronet-go checkout FETCH_HEAD
git -C ~/cronet-go submodule update --init --recursive --depth=1
- name: Regenerate Debian keyring
if: matrix.naive
run: |
set -xeuo pipefail
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
cd ~/cronet-go
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
- name: Cache Chromium toolchain
if: matrix.naive
id: cache-chromium-toolchain
uses: actions/cache@v4
with:
path: |
~/cronet-go/naiveproxy/src/third_party/llvm-build/
~/cronet-go/naiveproxy/src/gn/out/
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
~/cronet-go/naiveproxy/src/out/sysroot-build/
~/cronet-go/naiveproxy/src/third_party/llvm-build/Release+Asserts
~/cronet-go/naiveproxy/src/out/sysroot-build
key: chromium-toolchain-${{ matrix.arch }}-musl-${{ hashFiles('.github/CRONET_GO_VERSION') }}
- name: Download Chromium toolchain
if: matrix.naive
@@ -120,30 +116,24 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0'
if [[ "${{ matrix.naive }}" == "true" ]]; then
TAGS="$(cat release/DEFAULT_BUILD_TAGS),with_musl"
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
TAGS="${TAGS},with_naive_outbound,with_musl"
fi
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Set shared ldflags
run: |
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "${GITHUB_ENV}"
- name: Build (naive)
if: matrix.naive
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (non-naive)
if: ${{ ! matrix.naive }}
@@ -151,7 +141,7 @@ jobs:
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' ${LDFLAGS_SHARED} -s -w -buildid=" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
@@ -162,8 +152,14 @@ jobs:
- name: Set mtime
run: |-
TZ=UTC touch -t '197001010000' dist/sing-box
- name: Detect track
run: bash .github/detect_track.sh
- name: Set name
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
run: |-
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"

3
.gitignore vendored
View File

@@ -12,9 +12,6 @@
/*.jar
/*.aar
/*.xcframework/
/experimental/libbox/*.aar
/experimental/libbox/*.xcframework/
/experimental/libbox/*.nupkg
.DS_Store
/config.d/
/venv/

View File

@@ -9,11 +9,6 @@ run:
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_ccm
- with_ocm
- badlinkname
- tfogo_checklinkname0
linters:
default: none
enable:

View File

@@ -12,11 +12,10 @@ RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
&& go build -v -trimpath -tags "$TAGS" \
&& go build -v -trimpath -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" \
-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\" -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0" \
./cmd/sing-box
FROM --platform=$TARGETPLATFORM alpine AS dist
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"

View File

@@ -1,14 +1,8 @@
ARG BASE_IMAGE=alpine
FROM ${BASE_IMAGE}
FROM alpine
ARG TARGETARCH
ARG TARGETVARIANT
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
RUN set -ex \
&& if command -v apk > /dev/null; then \
apk add --no-cache --upgrade bash tzdata ca-certificates nftables; \
else \
apt-get update && apt-get install -y --no-install-recommends bash tzdata ca-certificates nftables \
&& rm -rf /var/lib/apt/lists/*; \
fi
&& apk add --no-cache --upgrade bash tzdata ca-certificates nftables
COPY sing-box-${TARGETARCH}${TARGETVARIANT} /usr/local/bin/sing-box
ENTRYPOINT ["sing-box"]

View File

@@ -1,18 +1,15 @@
NAME = sing-box
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)
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)
LDFLAGS_SHARED = $(shell cat release/LDFLAGS)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' $(LDFLAGS_SHARED) -s -w -buildid="
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -s -w -buildid= -checklinkname=0"
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)
SING_FFI ?= sing-ffi
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
.PHONY: test release docs build
@@ -92,12 +89,12 @@ update_android_version:
go run ./cmd/internal/update_android_version
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:
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/otherLegacy/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/other/release/*-universal.apk dist/release_android
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
rm -rf dist/release_android
@@ -209,12 +206,12 @@ update_apple_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
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
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:
go run -v ./cmd/internal/app_store_connect prepare_app_store
@@ -237,21 +234,22 @@ test_stdio:
lib_android:
go run ./cmd/internal/build_libbox -target android
lib_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_windows:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type csharp
lib_ios:
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
lib_android_new:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type android
lib_apple_new:
$(SING_FFI) generate --config $(LIBBOX_FFI_CONFIG) --platform-type apple
lib:
go run ./cmd/internal/build_libbox -target android
go run ./cmd/internal/build_libbox -target ios
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.12
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.12
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.11
docs:
venv/bin/mkdocs serve
@@ -260,8 +258,8 @@ publish_docs:
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
docs_install:
python3 -m venv venv
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.7.2" mkdocs-static-i18n=="1.2.*"
python -m venv venv
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
clean:
rm -rf bin dist sing-box
@@ -271,6 +269,3 @@ update:
git fetch
git reset FETCH_HEAD --hard
git clean -fdx
%:
@:

View File

@@ -9,10 +9,6 @@ import (
type ConnectionManager interface {
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)
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"encoding/binary"
"io"
"time"
"github.com/sagernet/sing/common/observable"
@@ -69,11 +68,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.Content)))
if err != nil {
return nil, err
}
_, err = buffer.Write(s.Content)
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
if err != nil {
return nil, err
}
@@ -81,11 +76,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
if err != nil {
return nil, err
}
_, err = varbin.WriteUvarint(&buffer, uint64(len(s.LastEtag)))
if err != nil {
return nil, err
}
_, err = buffer.WriteString(s.LastEtag)
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
if err != nil {
return nil, err
}
@@ -99,12 +90,7 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
if err != nil {
return err
}
contentLength, err := binary.ReadUvarint(reader)
if err != nil {
return err
}
s.Content = make([]byte, contentLength)
_, err = io.ReadFull(reader, s.Content)
err = varbin.Read(reader, binary.BigEndian, &s.Content)
if err != nil {
return err
}
@@ -114,16 +100,10 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
return err
}
s.LastUpdated = time.Unix(lastUpdated, 0)
etagLength, err := binary.ReadUvarint(reader)
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
if err != nil {
return err
}
etagBytes := make([]byte, etagLength)
_, err = io.ReadFull(reader, etagBytes)
if err != nil {
return err
}
s.LastEtag = string(etagBytes)
return nil
}

View File

@@ -62,10 +62,13 @@ type InboundContext struct {
// cache
// Deprecated: implement in rule action
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
RouteOriginalDestination M.Socksaddr
// Deprecated: to be removed
//nolint:staticcheck
InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool
UDPConnect bool
UDPTimeout time.Duration
@@ -101,10 +104,6 @@ type InboundContext struct {
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.IPCIDRAcceptEmpty = false
c.ResetRuleMatchCache()
}
func (c *InboundContext) ResetRuleMatchCache() {
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false

View File

@@ -47,11 +47,11 @@ type FindConnectionOwnerRequest struct {
}
type ConnectionOwner struct {
ProcessID uint32
UserId int32
UserName string
ProcessPath string
AndroidPackageNames []string
ProcessID uint32
UserId int32
UserName string
ProcessPath string
AndroidPackageName string
}
type Notification struct {

15
box.go
View File

@@ -19,6 +19,7 @@ import (
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"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/cachefile"
"github.com/sagernet/sing-box/log"
@@ -124,10 +125,7 @@ func New(options Options) (*Box, error) {
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
if err != nil {
return nil, err
}
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needCacheFile bool
var needClashAPI bool
var needV2RayAPI bool
@@ -325,12 +323,11 @@ func New(options Options) (*Box, error) {
)
})
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
return dnsTransportRegistry.CreateDNSTransport(
return local.NewTransport(
ctx,
logFactory.NewLogger("dns/local"),
"local",
C.DNSTypeLocal,
&option.LocalDNSServerOptions{},
option.LocalDNSServerOptions{},
)
})
if platformInterface != nil {
@@ -555,10 +552,6 @@ func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound
}
func (s *Box) Endpoint() adapter.EndpointManager {
return s.endpoint
}
func (s *Box) LogFactory() log.Factory {
return s.logFactory
}

View File

@@ -100,32 +100,11 @@ findVersion:
}
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()
if err != nil {
return err
}
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)
log.Info(tag, " list build IDs")
@@ -136,76 +115,97 @@ func publishTestflight(ctx context.Context) error {
buildIDs := common.Map(buildIDsResponse.Data, func(it asc.RelationshipData) string {
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
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
build := builds.Data[0]
log.Info(string(platform), " ", tag, " found build: ", build.ID, " (", *build.Attributes.Version, ")")
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
waitingForProcess = true
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(releaseNotes))
for _, platform := range platforms {
log.Info(string(platform), " list builds")
for {
builds, _, err := client.Builds.ListBuilds(ctx, &asc.ListBuildsQuery{
FilterApp: []string{appID},
FilterPreReleaseVersionPlatform: []string{string(platform)},
})
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
build := builds.Data[0]
if !waitingForProcess && (common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute) {
log.Info(string(platform), " ", tag, " waiting for process")
time.Sleep(15 * time.Second)
continue
}
if *build.Attributes.ProcessingState != "VALID" {
waitingForProcess = true
log.Info(string(platform), " ", tag, " waiting for process: ", *build.Attributes.ProcessingState)
time.Sleep(15 * time.Second)
continue
}
log.Info(string(platform), " ", tag, " list localizations")
localizations, _, err := client.TestFlight.ListBetaBuildLocalizationsForBuild(ctx, build.ID, nil)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
return err
}
localization := common.Find(localizations.Data, func(it asc.BetaBuildLocalization) bool {
return *it.Attributes.Locale == "en-US"
})
if localization.ID == "" {
log.Fatal(string(platform), " ", tag, " no en-US localization found")
}
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.TestFlight.UpdateBetaBuildLocalization(ctx, localization.ID, common.Ptr(
F.ToString("sing-box ", tagVersion.String()),
))
if err != nil {
return err
}
}
log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
log.Info("waiting for process")
time.Sleep(15 * time.Second)
continue
} else if err != nil {
return err
}
log.Info(string(platform), " ", tag, " list submissions")
betaSubmissions, _, err := client.TestFlight.ListBetaAppReviewSubmissions(ctx, &asc.ListBetaAppReviewSubmissionsQuery{
FilterBuild: []string{build.ID},
})
if err != nil {
return err
}
if len(betaSubmissions.Data) == 0 {
log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err
}
}
break
}
break
}
return nil
}

View File

@@ -17,17 +17,17 @@ import (
)
var (
debugEnabled bool
target string
platform string
// withTailscale bool
debugEnabled bool
target string
platform string
withTailscale bool
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target 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() {
@@ -48,7 +48,7 @@ var (
debugFlags []string
sharedTags []string
darwinTags []string
// memcTags []string
memcTags []string
notMemcTags []string
debugTags []string
)
@@ -63,10 +63,9 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
// memcTags = append(memcTags, "with_tailscale")
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
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")
memcTags = append(memcTags, "with_tailscale")
notMemcTags = append(notMemcTags, "with_low_memory")
debugTags = append(debugTags, "debug")
}
@@ -165,7 +164,7 @@ func buildAndroid() {
// Build main variant (SDK 23)
mainTags := append([]string{}, sharedTags...)
// mainTags = append(mainTags, memcTags...)
mainTags = append(mainTags, memcTags...)
if debugEnabled {
mainTags = append(mainTags, debugTags...)
}
@@ -177,7 +176,7 @@ func buildAndroid() {
// Build legacy variant (SDK 21, no naive outbound)
legacyTags := filterTags(sharedTags, "with_naive_outbound")
// legacyTags = append(legacyTags, memcTags...)
legacyTags = append(legacyTags, memcTags...)
if debugEnabled {
legacyTags = append(legacyTags, debugTags...)
}
@@ -205,9 +204,9 @@ func buildApple() {
"-libname=box",
"-tags-not-macos=with_low_memory",
}
//if !withTailscale {
// args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
//}
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
}
if !debugEnabled {
args = append(args, sharedFlags...)
@@ -216,9 +215,9 @@ func buildApple() {
}
tags := append(sharedTags, darwinTags...)
//if withTailscale {
// tags = append(tags, memcTags...)
//}
if withTailscale {
tags = append(tags, memcTags...)
}
if debugEnabled {
tags = append(tags, debugTags...)
}

View File

@@ -71,12 +71,12 @@ func findAndReplace(objectsMap map[string]any, projectContent string, bundleIDLi
indexEnd := indexStart + strings.Index(projectContent[indexStart:], "}")
versionStart := indexStart + strings.Index(projectContent[indexStart:indexEnd], "MARKETING_VERSION = ") + 20
versionEnd := versionStart + strings.Index(projectContent[versionStart:indexEnd], ";")
version := strings.Trim(projectContent[versionStart:versionEnd], "\"")
version := projectContent[versionStart:versionEnd]
if version == newVersion {
continue
}
updated = true
projectContent = projectContent[:versionStart] + "\"" + newVersion + "\"" + projectContent[versionEnd:]
projectContent = projectContent[:versionStart] + newVersion + projectContent[versionEnd:]
}
return projectContent, updated
}

54
common/conntrack/conn.go Normal file
View 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
}

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

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

View File

@@ -0,0 +1,5 @@
//go:build !with_conntrack
package conntrack
const Enabled = false

View File

@@ -0,0 +1,5 @@
//go:build with_conntrack
package conntrack
const Enabled = true

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/listener"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
@@ -36,7 +37,6 @@ type DefaultDialer struct {
udpAddr4 string
udpAddr6 string
netns string
connectionManager adapter.ConnectionManager
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
@@ -47,7 +47,6 @@ type DefaultDialer struct {
}
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
connectionManager := service.FromContext[adapter.ConnectionManager](ctx)
networkManager := service.FromContext[adapter.NetworkManager](ctx)
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
@@ -90,7 +89,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if networkManager != nil {
defaultOptions := networkManager.DefaultOptions()
if defaultOptions.BindInterface != "" && !disableDefaultBind {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.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))
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 {
dialer.Timeout = time.Duration(options.ConnectTimeout)
} else {
dialer.Timeout = C.TCPConnectTimeout
}
if options.DisableTCPKeepAlive {
dialer.KeepAlive = -1
dialer.KeepAliveConfig.Enable = false
} else {
if !options.DisableTCPKeepAlive {
keepIdle := time.Duration(options.TCPKeepAlive)
if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial
@@ -161,11 +151,8 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if keepInterval == 0 {
keepInterval = C.TCPKeepAliveInterval
}
dialer.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: keepIdle,
Interval: keepInterval,
}
dialer.KeepAlive = keepIdle
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(keepIdle, keepInterval))
}
var udpFragment bool
if options.UDPFragment != nil {
@@ -213,7 +200,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
netns: options.NetNs,
connectionManager: connectionManager,
networkManager: networkManager,
networkStrategy: networkStrategy,
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) {
if !address.IsValid() {
return nil, E.New("invalid address")
} else if address.IsDomain() {
} else if address.IsFqdn() {
return nil, E.New("domain not resolved")
}
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) {
case N.NetworkUDP:
if !address.IsIPv6() {
@@ -311,12 +297,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
if !fastFallback && !isPrimary {
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) {
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() {
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
} 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 {
if !destination.Is6() {
return d.dialer4.Dialer
} else {
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 d.trackPacketConn(packetConn, nil)
return trackPacketConn(packetConn, nil)
}
func (d *DefaultDialer) WireGuardControl() control.Func {
return d.udpListener.Control
}
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
if d.connectionManager == nil || err != nil {
func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil {
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) {
if d.connectionManager == nil || err != nil {
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if !conntrack.Enabled || err != nil {
return conn, err
}
return d.connectionManager.TrackPacketConn(conn), nil
return conntrack.NewPacketConn(conn)
}

View File

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

View File

@@ -96,7 +96,7 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
if err != nil {
return nil, err
}
if !destination.IsDomain() {
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
@@ -116,7 +116,7 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil {
return nil, err
}
if !destination.IsDomain() {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
@@ -144,7 +144,7 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
if err != nil {
return nil, err
}
if !destination.IsDomain() {
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
@@ -167,7 +167,7 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
if err != nil {
return nil, err
}
if !destination.IsDomain() {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)

View File

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

View File

@@ -9,6 +9,7 @@ import (
"sync/atomic"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
type Reader struct {
@@ -77,7 +78,7 @@ func (r *Reader) readMetadata() error {
codeIndex uint64
codeLength uint64
)
code, err = readString(reader)
code, err = varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return err
}
@@ -111,16 +112,9 @@ func (r *Reader) Read(code string) ([]Item, error) {
}
r.bufferedReader.Reset(r.reader)
itemList := make([]Item, r.domainLength[code])
for i := range itemList {
typeByte, err := r.bufferedReader.ReadByte()
if err != nil {
return nil, err
}
itemList[i].Type = ItemType(typeByte)
itemList[i].Value, err = readString(r.bufferedReader)
if err != nil {
return nil, err
}
err = varbin.Read(r.bufferedReader, binary.BigEndian, &itemList)
if err != nil {
return nil, err
}
return itemList, nil
}
@@ -141,18 +135,3 @@ func (r *readCounter) Read(p []byte) (n int, err error) {
}
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
}

View File

@@ -2,6 +2,7 @@ package geosite
import (
"bytes"
"encoding/binary"
"sort"
"github.com/sagernet/sing/common/varbin"
@@ -19,11 +20,7 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
for _, code := range keys {
index[code] = content.Len()
for _, item := range domains[code] {
err := content.WriteByte(byte(item.Type))
if err != nil {
return err
}
err = writeString(content, item.Value)
err := varbin.Write(content, binary.BigEndian, item)
if err != nil {
return err
}
@@ -41,7 +38,7 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
}
for _, code := range keys {
err = writeString(writer, code)
err = varbin.Write(writer, binary.BigEndian, code)
if err != nil {
return err
}
@@ -62,12 +59,3 @@ func Write(writer varbin.Writer, domains map[string][]Item) error {
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
}

View File

@@ -12,7 +12,6 @@ import (
"fmt"
"io"
"net"
"unsafe"
)
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)
data, typ, err = c.rawConn.In.Decrypt(record)
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

View File

@@ -151,7 +151,6 @@ func ListenNetworkNamespace[T any](nameOrPath string, block func() (T, error)) (
if err != nil {
return common.DefaultValue[T](), E.Cause(err, "get current netns")
}
defer currentNs.Close()
defer netns.Set(currentNs)
var targetNs netns.NsHandle
if strings.HasPrefix(nameOrPath, "/") {

View File

@@ -37,10 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if l.listenOptions.ReuseAddr {
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
}
if l.listenOptions.DisableTCPKeepAlive {
listenConfig.KeepAlive = -1
listenConfig.KeepAliveConfig.Enable = false
} else {
if !l.listenOptions.DisableTCPKeepAlive {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial
@@ -102,6 +99,8 @@ func (l *Listener) loopTCPIn() {
}
//nolint:staticcheck
metadata.InboundDetour = l.listenOptions.Detour
//nolint:staticcheck
metadata.InboundOptions = l.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
ctx := log.ContextWithNewID(l.ctx)

View File

@@ -14,7 +14,6 @@ import (
type Searcher interface {
FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error)
Close() error
}
var ErrNotFound = E.New("process not found")
@@ -29,7 +28,7 @@ func FindProcessInfo(searcher Searcher, ctx context.Context, network string, sou
if err != nil {
return nil, err
}
if info.UserId != -1 && info.UserName == "" {
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.UserName = osUser.Username

View File

@@ -6,7 +6,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
)
var _ Searcher = (*androidSearcher)(nil)
@@ -19,30 +18,22 @@ func NewSearcher(config Config) (Searcher, error) {
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) {
family, protocol, err := socketDiagSettings(network, source)
_, uid, err := resolveSocketByNetlink(network, source, destination)
if err != nil {
return nil, err
}
_, uid, err := querySocketDiagOnce(family, protocol, source)
if err != nil {
return nil, err
if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded {
return &adapter.ConnectionOwner{
UserId: int32(uid),
AndroidPackageName: sharedPackage,
}, nil
}
appID := uid % 100000
var packageNames []string
if sharedPackage, loaded := s.packageManager.SharedPackageByID(appID); loaded {
packageNames = append(packageNames, sharedPackage)
if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded {
return &adapter.ConnectionOwner{
UserId: int32(uid),
AndroidPackageName: packageName,
}, nil
}
if packages, loaded := s.packageManager.PackagesByID(appID); loaded {
packageNames = append(packageNames, packages...)
}
packageNames = common.Uniq(packageNames)
return &adapter.ConnectionOwner{
UserId: int32(uid),
AndroidPackageNames: packageNames,
}, nil
return &adapter.ConnectionOwner{UserId: int32(uid)}, nil
}

View File

@@ -1,15 +1,19 @@
//go:build darwin
package process
import (
"context"
"encoding/binary"
"net/netip"
"os"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/sagernet/sing-box/adapter"
N "github.com/sagernet/sing/common/network"
"golang.org/x/sys/unix"
)
var _ Searcher = (*darwinSearcher)(nil)
@@ -20,12 +24,12 @@ func NewSearcher(_ Config) (Searcher, error) {
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) {
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 {
@@ -43,3 +47,107 @@ var structSize = func() int {
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]))
}

View File

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

View File

@@ -4,82 +4,33 @@ package process
import (
"context"
"errors"
"net/netip"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
)
var _ Searcher = (*linuxSearcher)(nil)
type linuxSearcher struct {
logger log.ContextLogger
diagConns [4]*socketDiagConn
processPathCache *uidProcessPathCache
logger log.ContextLogger
}
func NewSearcher(config Config) (Searcher, error) {
searcher := &linuxSearcher{
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...)
return &linuxSearcher{config.Logger}, nil
}
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 {
return nil, err
}
processInfo := &adapter.ConnectionOwner{
UserId: int32(uid),
}
processPath, err := s.processPathCache.findProcessPath(inode, uid)
processPath, err := resolveProcessNameByProcSearch(inode, uid)
if err != nil {
s.logger.DebugContext(ctx, "find process path: ", err)
} else {
processInfo.ProcessPath = processPath
}
return processInfo, nil
}
func (s *linuxSearcher) resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
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)
return &adapter.ConnectionOwner{
UserId: int32(uid),
ProcessPath: processPath,
}, nil
}

View File

@@ -3,67 +3,43 @@
package process
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"net"
"net/netip"
"os"
"path/filepath"
"path"
"strings"
"sync"
"syscall"
"time"
"unicode"
"unsafe"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
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 (
sizeOfSocketDiagRequestData = 56
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + sizeOfSocketDiagRequestData
socketDiagResponseMinSize = 72
socketDiagByFamily = 20
pathProc = "/proc"
sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48
socketDiagByFamily = 20
pathProc = "/proc"
)
type socketDiagConn struct {
access sync.Mutex
family uint8
protocol uint8
fd int
}
func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) {
var family uint8
var protocol uint8
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 {
case N.NetworkTCP:
protocol = syscall.IPPROTO_TCP
@@ -72,308 +48,151 @@ func socketDiagSettings(network string, source netip.AddrPort) (family, protocol
default:
return 0, 0, os.ErrInvalid
}
switch {
case source.Addr().Is4():
if source.Addr().Is4() {
family = syscall.AF_INET
case source.Addr().Is6():
} else {
family = syscall.AF_INET6
default:
return 0, 0, os.ErrInvalid
}
return family, protocol, nil
}
func newUIDProcessPathCache(ttl time.Duration) *uidProcessPathCache {
cache := common.Must1(freelru.NewSharded[uint32, *uidProcessPaths](64, maphash.NewHasher[uint32]().Hash32))
cache.SetLifetime(ttl)
return &uidProcessPathCache{cache: cache}
}
req := packSocketDiagRequest(family, protocol, source)
func (c *uidProcessPathCache) findProcessPath(targetInode, uid uint32) (string, error) {
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 {
return 0, 0, E.Cause(err, "dial netlink")
}
inode, uid, err = querySocketDiag(c.fd, request)
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) {
fd, err := openSocketDiag()
socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG)
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))
}
defer syscall.Close(socket)
func (c *socketDiagConn) ensureOpenLocked() error {
if c.fd != -1 {
return nil
}
fd, err := openSocketDiag()
if err != nil {
return err
}
c.fd = fd
return nil
}
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 100})
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 100})
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{
err = syscall.Connect(socket, &syscall.SockaddrNetlink{
Family: syscall.AF_NETLINK,
Pad: 0,
Pid: 0,
Groups: 0,
}); err != nil {
syscall.Close(fd)
return -1, err
})
if err != nil {
return
}
return fd, nil
}
func (c *socketDiagConn) closeLocked() error {
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)
_, err = syscall.Write(socket, req)
if err != nil {
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 {
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 {
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) {
for _, message := range messages {
switch message.Header.Type {
case syscall.NLMSG_DONE:
continue
case syscall.NLMSG_ERROR:
err = unpackSocketDiagError(&message)
if err != nil {
return 0, 0, err
}
case socketDiagByFamily:
inode, uid = unpackSocketDiagResponse(&message)
if inode != 0 || uid != 0 {
return inode, uid, nil
}
}
}
return 0, 0, ErrNotFound
func packSocketDiagRequest(family, protocol byte, source netip.AddrPort) []byte {
s := make([]byte, 16)
copy(s, source.Addr().AsSlice())
buf := make([]byte, sizeOfSocketDiagRequest)
nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest)
nativeEndian.PutUint16(buf[4:6], socketDiagByFamily)
nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP)
nativeEndian.PutUint32(buf[8:12], 0)
nativeEndian.PutUint32(buf[12:16], 0)
buf[16] = family
buf[17] = protocol
buf[18] = 0
buf[19] = 0
nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF)
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) {
if len(msg.Data) < socketDiagResponseMinSize {
if len(msg.Data) < 72 {
return 0, 0
}
uid = binary.NativeEndian.Uint32(msg.Data[64:68])
inode = binary.NativeEndian.Uint32(msg.Data[68:72])
return inode, uid
data := msg.Data
uid = nativeEndian.Uint32(data[64:68])
inode = nativeEndian.Uint32(data[68:72])
return
}
func unpackSocketDiagError(msg *syscall.NetlinkMessage) 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) {
func resolveProcessNameByProcSearch(inode, uid uint32) (string, error) {
files, err := os.ReadDir(pathProc)
if err != nil {
return nil, err
return "", err
}
buffer := make([]byte, syscall.PathMax)
processPaths := make(map[uint32]string)
for _, file := range files {
if !file.IsDir() || !isPid(file.Name()) {
socket := []byte(fmt.Sprintf("socket:[%d]", inode))
for _, f := range files {
if !f.IsDir() || !isPid(f.Name()) {
continue
}
info, err := file.Info()
info, err := f.Info()
if err != nil {
if isIgnorableProcError(err) {
continue
}
return nil, err
return "", err
}
if info.Sys().(*syscall.Stat_t).Uid != uid {
continue
}
processPath := filepath.Join(pathProc, file.Name())
fdPath := filepath.Join(processPath, "fd")
exePath, err := os.Readlink(filepath.Join(processPath, "exe"))
if err != nil {
if isIgnorableProcError(err) {
continue
}
return nil, err
}
processPath := path.Join(pathProc, f.Name())
fdPath := path.Join(processPath, "fd")
fds, err := os.ReadDir(fdPath)
if err != nil {
continue
}
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 {
continue
}
inode, ok := parseSocketInode(buffer[:n])
if !ok {
continue
}
if _, loaded := processPaths[inode]; !loaded {
processPaths[inode] = exePath
if bytes.Equal(buffer[:n], socket) {
return os.Readlink(path.Join(processPath, "exe"))
}
}
}
return processPaths, nil
}
func isIgnorableProcError(err error) bool {
return os.IsNotExist(err) || os.IsPermission(err)
}
func parseSocketInode(link []byte) (uint32, bool) {
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
return "", fmt.Errorf("process of uid(%d),inode(%d) not found", uid, inode)
}
func isPid(s string) bool {

View File

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

View File

@@ -28,10 +28,6 @@ func initWin32API() error {
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) {
pid, err := winiphlpapi.FindPid(network, source)
if err != nil {

View File

@@ -6,7 +6,6 @@ import (
"encoding/binary"
"io"
"net/netip"
"unsafe"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
@@ -506,24 +505,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
}
func readRuleItemString(reader varbin.Reader) ([]string, error) {
length, err := binary.ReadUvarint(reader)
if err != nil {
return nil, err
}
result := make([]string, length)
for i := range result {
strLen, err := binary.ReadUvarint(reader)
if err != nil {
return nil, err
}
buf := make([]byte, strLen)
_, err = io.ReadFull(reader, buf)
if err != nil {
return nil, err
}
result[i] = string(buf)
}
return result, nil
return varbin.ReadValue[[]string](reader, binary.BigEndian)
}
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
@@ -531,34 +513,11 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
if err != nil {
return err
}
_, 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
return varbin.Write(writer, binary.BigEndian, value)
}
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
length, err := binary.ReadUvarint(reader)
if err != nil {
return nil, err
}
result := make([]E, length)
_, err = io.ReadFull(reader, *(*[]byte)(unsafe.Pointer(&result)))
if err != nil {
return nil, err
}
return result, nil
return varbin.ReadValue[[]E](reader, binary.BigEndian)
}
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
@@ -566,25 +525,11 @@ func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []
if err != nil {
return err
}
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
if err != nil {
return err
}
_, err = writer.Write(*(*[]byte)(unsafe.Pointer(&value)))
return err
return varbin.Write(writer, binary.BigEndian, value)
}
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
length, err := binary.ReadUvarint(reader)
if err != nil {
return nil, err
}
result := make([]uint16, length)
err = binary.Read(reader, binary.BigEndian, result)
if err != nil {
return nil, err
}
return result, nil
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
}
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
@@ -592,11 +537,7 @@ func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) e
if err != nil {
return err
}
_, err = varbin.WriteUvarint(writer, uint64(len(value)))
if err != nil {
return err
}
return binary.Write(writer, binary.BigEndian, value)
return varbin.Write(writer, binary.BigEndian, value)
}
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {

View File

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

View File

@@ -2,7 +2,6 @@ package srs
import (
"encoding/binary"
"io"
"net/netip"
M "github.com/sagernet/sing/common/metadata"
@@ -10,16 +9,11 @@ import (
)
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
addrLen, err := binary.ReadUvarint(reader)
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
if err != nil {
return netip.Prefix{}, err
}
addrSlice := make([]byte, addrLen)
_, err = io.ReadFull(reader, addrSlice)
if err != nil {
return netip.Prefix{}, err
}
prefixBits, err := reader.ReadByte()
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
if err != nil {
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 {
addrSlice := prefix.Addr().AsSlice()
_, err := varbin.WriteUvarint(writer, uint64(len(addrSlice)))
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
if err != nil {
return err
}
_, err = writer.Write(addrSlice)
if err != nil {
return err
}
err = writer.WriteByte(uint8(prefix.Bits()))
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
if err != nil {
return err
}

View File

@@ -2,11 +2,11 @@ package srs
import (
"encoding/binary"
"io"
"net/netip"
"os"
"unsafe"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/varbin"
@@ -22,6 +22,11 @@ type myIPRange struct {
to netip.Addr
}
type myIPRangeData struct {
From []byte
To []byte
}
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
version, err := reader.ReadByte()
if err != nil {
@@ -36,30 +41,17 @@ func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
if err != nil {
return nil, err
}
mySet := &myIPSet{
rr: make([]myIPRange, length),
ranges := make([]myIPRangeData, length)
err = varbin.Read(reader, binary.BigEndian, &ranges)
if err != nil {
return nil, err
}
for i := range mySet.rr {
fromLen, err := binary.ReadUvarint(reader)
if err != nil {
return nil, err
}
fromBytes := make([]byte, fromLen)
_, err = io.ReadFull(reader, fromBytes)
if err != nil {
return nil, err
}
toLen, err := binary.ReadUvarint(reader)
if err != nil {
return nil, err
}
toBytes := make([]byte, toLen)
_, err = io.ReadFull(reader, toBytes)
if err != nil {
return nil, err
}
mySet.rr[i].from = M.AddrFromIP(fromBytes)
mySet.rr[i].to = M.AddrFromIP(toBytes)
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
}
@@ -69,27 +61,18 @@ func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {
if err != nil {
return err
}
mySet := (*myIPSet)(unsafe.Pointer(set))
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData {
return myIPRangeData{
From: rr.from.AsSlice(),
To: rr.to.AsSlice(),
}
})
err = binary.Write(writer, binary.BigEndian, uint64(len(dataList)))
if err != nil {
return err
}
for _, rr := range mySet.rr {
fromBytes := rr.from.AsSlice()
_, err = varbin.WriteUvarint(writer, uint64(len(fromBytes)))
if err != nil {
return err
}
_, err = writer.Write(fromBytes)
if err != nil {
return err
}
toBytes := rr.to.AsSlice()
_, err = varbin.WriteUvarint(writer, uint64(len(toBytes)))
if err != nil {
return err
}
_, err = writer.Write(toBytes)
for _, data := range dataList {
err = varbin.Write(writer, binary.BigEndian, data)
if err != nil {
return err
}

View File

@@ -14,7 +14,6 @@ import (
"github.com/sagernet/sing/common/logger"
"github.com/caddyserver/certmagic"
"github.com/libdns/acmedns"
"github.com/libdns/alidns"
"github.com/libdns/cloudflare"
"github.com/mholt/acmez/v3/acme"
@@ -127,13 +126,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
APIToken: dnsOptions.CloudflareOptions.APIToken,
ZoneToken: dnsOptions.CloudflareOptions.ZoneToken,
}
case C.DNSProviderACMEDNS:
solver.DNSProvider = &acmedns.Provider{
Username: dnsOptions.ACMEDNSOptions.Username,
Password: dnsOptions.ACMEDNSOptions.Password,
Subdomain: dnsOptions.ACMEDNSOptions.Subdomain,
ServerURL: dnsOptions.ACMEDNSOptions.ServerURL,
}
default:
return nil, nil, E.New("unsupported ACME DNS01 provider type: " + dnsOptions.Provider)
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
aTLS "github.com/sagernet/sing/common/tls"
@@ -37,7 +38,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
}
//nolint:staticcheck
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 {
block, rest := pem.Decode(echConfig)
@@ -76,7 +77,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
tlsConfig.EncryptedClientHelloKeys = echKeys
//nolint:staticcheck
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
}

View File

@@ -33,5 +33,4 @@ const (
const (
DNSProviderAliDNS = "alidns"
DNSProviderCloudflare = "cloudflare"
DNSProviderACMEDNS = "acmedns"
)

View File

@@ -30,7 +30,6 @@ const (
TypeSSMAPI = "ssm-api"
TypeCCM = "ccm"
TypeOCM = "ocm"
TypeOOMKiller = "oom-killer"
)
const (
@@ -86,8 +85,6 @@ func ProxyDisplayName(proxyType string) string {
return "Hysteria2"
case TypeAnyTLS:
return "AnyTLS"
case TypeTailscale:
return "Tailscale"
case TypeSelector:
return "Selector"
case TypeURLTest:

View File

@@ -7,12 +7,9 @@ import (
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/service"
@@ -23,7 +20,6 @@ type Instance struct {
ctx context.Context
cancel context.CancelFunc
instance *box.Box
connectionManager adapter.ConnectionManager
clashServer adapter.ClashServer
cacheFile adapter.CacheFile
pauseManager pause.Manager
@@ -87,15 +83,6 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
}
}
}
if s.oomKiller && C.IsIos {
if !common.Any(options.Services, func(it option.Service) bool {
return it.Type == C.TypeOOMKiller
}) {
options.Services = append(options.Services, option.Service{
Type: C.TypeOOMKiller,
})
}
}
urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
i := &Instance{
@@ -113,11 +100,9 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
return nil, err
}
i.instance = boxInstance
i.connectionManager = service.FromContext[adapter.ConnectionManager](ctx)
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
i.pauseManager = service.FromContext[pause.Manager](ctx)
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
log.SetStdLogger(boxInstance.LogFactory().Logger())
return i, nil
}

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/experimental/clashapi"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
@@ -35,7 +36,6 @@ type StartedService struct {
handler PlatformHandler
debug bool
logMaxLines int
oomKiller bool
// workingDirectory string
// tempDirectory string
// userID int
@@ -56,9 +56,6 @@ type StartedService struct {
urlTestHistoryStorage *urltest.HistoryStorage
clashModeSubscriber *observable.Subscriber[struct{}]
clashModeObserver *observable.Observer[struct{}]
connectionEventSubscriber *observable.Subscriber[trafficontrol.ConnectionEvent]
connectionEventObserver *observable.Observer[trafficontrol.ConnectionEvent]
}
type ServiceOptions struct {
@@ -67,7 +64,6 @@ type ServiceOptions struct {
Handler PlatformHandler
Debug bool
LogMaxLines int
OOMKiller bool
// WorkingDirectory string
// TempDirectory string
// UserID int
@@ -82,25 +78,22 @@ func NewStartedService(options ServiceOptions) *StartedService {
handler: options.Handler,
debug: options.Debug,
logMaxLines: options.LogMaxLines,
oomKiller: options.OOMKiller,
// workingDirectory: options.WorkingDirectory,
// tempDirectory: options.TempDirectory,
// userID: options.UserID,
// groupID: options.GroupID,
// systemProxyEnabled: options.SystemProxyEnabled,
serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE},
serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4),
logSubscriber: observable.NewSubscriber[*log.Entry](128),
urlTestSubscriber: observable.NewSubscriber[struct{}](1),
urlTestHistoryStorage: urltest.NewHistoryStorage(),
clashModeSubscriber: observable.NewSubscriber[struct{}](1),
connectionEventSubscriber: observable.NewSubscriber[trafficontrol.ConnectionEvent](256),
serviceStatus: &ServiceStatus{Status: ServiceStatus_IDLE},
serviceStatusSubscriber: observable.NewSubscriber[*ServiceStatus](4),
logSubscriber: observable.NewSubscriber[*log.Entry](128),
urlTestSubscriber: observable.NewSubscriber[struct{}](1),
urlTestHistoryStorage: urltest.NewHistoryStorage(),
clashModeSubscriber: observable.NewSubscriber[struct{}](1),
}
s.serviceStatusObserver = observable.NewObserver(s.serviceStatusSubscriber, 2)
s.logObserver = observable.NewObserver(s.logSubscriber, 64)
s.urlTestObserver = observable.NewObserver(s.urlTestSubscriber, 1)
s.clashModeObserver = observable.NewObserver(s.clashModeSubscriber, 1)
s.connectionEventObserver = observable.NewObserver(s.connectionEventSubscriber, 64)
return s
}
@@ -168,7 +161,7 @@ func (s *StartedService) waitForStarted(ctx context.Context) error {
func (s *StartedService) StartOrReloadService(profileContent string, options *OverrideOptions) error {
s.serviceAccess.Lock()
switch s.serviceStatus.Status {
case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING, ServiceStatus_FATAL:
case ServiceStatus_IDLE, ServiceStatus_STARTED, ServiceStatus_STARTING:
default:
s.serviceAccess.Unlock()
return os.ErrInvalid
@@ -190,7 +183,6 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov
instance.urlTestHistoryStorage.SetHook(s.urlTestSubscriber)
if instance.clashServer != nil {
instance.clashServer.SetModeUpdateHook(s.clashModeSubscriber)
instance.clashServer.(*clashapi.Server).TrafficManager().SetEventHook(s.connectionEventSubscriber)
}
s.serviceAccess.Unlock()
err = instance.Start()
@@ -209,14 +201,6 @@ func (s *StartedService) StartOrReloadService(profileContent string, options *Ov
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 {
s.serviceAccess.Lock()
switch s.serviceStatus.Status {
@@ -226,14 +210,13 @@ func (s *StartedService) CloseService() error {
return os.ErrInvalid
}
s.updateStatus(ServiceStatus_STOPPING)
instance := s.instance
s.instance = nil
if instance != nil {
err := instance.Close()
if s.instance != nil {
err := s.instance.Close()
if err != nil {
return s.updateStatusError(err)
}
}
s.instance = nil
s.startedAt = time.Time{}
s.updateStatus(ServiceStatus_IDLE)
s.serviceAccess.Unlock()
@@ -410,14 +393,12 @@ func (s *StartedService) SubscribeStatus(request *SubscribeStatusRequest, server
func (s *StartedService) readStatus() *Status {
var status Status
status.Memory = memory.Total()
status.Memory = memory.Inuse()
status.Goroutines = int32(runtime.NumGoroutine())
status.ConnectionsOut = int32(conntrack.Count())
s.serviceAccess.RLock()
nowService := s.instance
s.serviceAccess.RUnlock()
if nowService != nil && nowService.connectionManager != nil {
status.ConnectionsOut = int32(nowService.connectionManager.Count())
}
if nowService != nil {
if clashServer := nowService.clashServer; clashServer != nil {
status.TrafficAvailable = true
@@ -685,7 +666,7 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
return nil, err
}
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[Connections]) error {
err := s.waitForStarted(server.Context())
if err != nil {
return err
@@ -693,271 +674,80 @@ func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsReque
s.serviceAccess.RLock()
boxService := s.instance
s.serviceAccess.RUnlock()
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)
ticker := time.NewTicker(time.Duration(request.Interval))
defer ticker.Stop()
trafficManager := boxService.clashServer.(*clashapi.Server).TrafficManager()
var (
connections = make(map[uuid.UUID]*Connection)
outConnections []*Connection
)
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 {
case <-s.ctx.Done():
return s.ctx.Err()
case <-server.Context().Done():
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:
protoEvents := s.buildTrafficUpdates(trafficManager, connectionSnapshots)
if len(protoEvents) == 0 {
continue
}
err = server.Send(&ConnectionEvents{Events: protoEvents})
if err != nil {
return err
}
}
}
}
type connectionSnapshot struct {
uplink int64
downlink int64
hadTraffic bool
}
func (s *StartedService) buildInitialConnectionState(manager *trafficontrol.Manager, snapshots map[uuid.UUID]connectionSnapshot) []*ConnectionEvent {
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(),
}
}
for _, metadata := range manager.ClosedConnections() {
conn := buildConnectionProto(metadata)
conn.ClosedAt = metadata.ClosedAt.UnixMilli()
events = append(events, &ConnectionEvent{
Type: ConnectionEventType_CONNECTION_EVENT_NEW,
Id: metadata.ID.String(),
Connection: conn,
})
}
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,
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) *Connection {
if oldConnection, loaded := connections[metadata.ID]; loaded {
if isClosed {
if oldConnection.ClosedAt == 0 {
oldConnection.Uplink = 0
oldConnection.Downlink = 0
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
}
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,
})
return oldConnection
}
lastUplink := oldConnection.UplinkTotal
lastDownlink := oldConnection.DownlinkTotal
uplinkTotal := metadata.Upload.Load()
downlinkTotal := metadata.Download.Load()
oldConnection.Uplink = uplinkTotal - lastUplink
oldConnection.Downlink = downlinkTotal - lastDownlink
oldConnection.UplinkTotal = uplinkTotal
oldConnection.DownlinkTotal = downlinkTotal
return oldConnection
}
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
if metadata.Rule != nil {
rule = metadata.Rule.String()
}
uplinkTotal := metadata.Upload.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
if metadata.Metadata.ProcessInfo != nil {
processInfo = &ProcessInfo{
ProcessId: metadata.Metadata.ProcessInfo.ProcessID,
UserId: metadata.Metadata.ProcessInfo.UserId,
UserName: metadata.Metadata.ProcessInfo.UserName,
ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath,
PackageNames: metadata.Metadata.ProcessInfo.AndroidPackageNames,
ProcessId: metadata.Metadata.ProcessInfo.ProcessID,
UserId: metadata.Metadata.ProcessInfo.UserId,
UserName: metadata.Metadata.ProcessInfo.UserName,
ProcessPath: metadata.Metadata.ProcessInfo.ProcessPath,
PackageName: metadata.Metadata.ProcessInfo.AndroidPackageName,
}
}
return &Connection{
connection := &Connection{
Id: metadata.ID.String(),
Inbound: metadata.Metadata.Inbound,
InboundType: metadata.Metadata.InboundType,
@@ -970,6 +760,9 @@ func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {
User: metadata.Metadata.User,
FromOutbound: metadata.Metadata.Outbound,
CreatedAt: metadata.CreatedAt.UnixMilli(),
ClosedAt: closedAt,
Uplink: uplink,
Downlink: downlink,
UplinkTotal: uplinkTotal,
DownlinkTotal: downlinkTotal,
Rule: rule,
@@ -978,6 +771,8 @@ func buildConnectionProto(metadata *trafficontrol.TrackerMetadata) *Connection {
ChainList: metadata.Chain,
ProcessInfo: processInfo,
}
connections[metadata.ID] = connection
return connection
}
func (s *StartedService) CloseConnection(ctx context.Context, request *CloseConnectionRequest) (*emptypb.Empty, error) {
@@ -998,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) {
s.serviceAccess.RLock()
nowService := s.instance
s.serviceAccess.RUnlock()
if nowService != nil && nowService.connectionManager != nil {
nowService.connectionManager.CloseAll()
}
conntrack.Close()
return &emptypb.Empty{}, nil
}

View File

@@ -78,55 +78,104 @@ func (LogLevel) EnumDescriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{0}
}
type ConnectionEventType int32
type ConnectionFilter int32
const (
ConnectionEventType_CONNECTION_EVENT_NEW ConnectionEventType = 0
ConnectionEventType_CONNECTION_EVENT_UPDATE ConnectionEventType = 1
ConnectionEventType_CONNECTION_EVENT_CLOSED ConnectionEventType = 2
ConnectionFilter_ALL ConnectionFilter = 0
ConnectionFilter_ACTIVE ConnectionFilter = 1
ConnectionFilter_CLOSED ConnectionFilter = 2
)
// Enum value maps for ConnectionEventType.
// Enum value maps for ConnectionFilter.
var (
ConnectionEventType_name = map[int32]string{
0: "CONNECTION_EVENT_NEW",
1: "CONNECTION_EVENT_UPDATE",
2: "CONNECTION_EVENT_CLOSED",
ConnectionFilter_name = map[int32]string{
0: "ALL",
1: "ACTIVE",
2: "CLOSED",
}
ConnectionEventType_value = map[string]int32{
"CONNECTION_EVENT_NEW": 0,
"CONNECTION_EVENT_UPDATE": 1,
"CONNECTION_EVENT_CLOSED": 2,
ConnectionFilter_value = map[string]int32{
"ALL": 0,
"ACTIVE": 1,
"CLOSED": 2,
}
)
func (x ConnectionEventType) Enum() *ConnectionEventType {
p := new(ConnectionEventType)
func (x ConnectionFilter) Enum() *ConnectionFilter {
p := new(ConnectionFilter)
*p = x
return p
}
func (x ConnectionEventType) String() string {
func (x ConnectionFilter) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ConnectionEventType) Descriptor() protoreflect.EnumDescriptor {
func (ConnectionFilter) Descriptor() protoreflect.EnumDescriptor {
return file_daemon_started_service_proto_enumTypes[1].Descriptor()
}
func (ConnectionEventType) Type() protoreflect.EnumType {
func (ConnectionFilter) Type() protoreflect.EnumType {
return &file_daemon_started_service_proto_enumTypes[1]
}
func (x ConnectionEventType) Number() protoreflect.EnumNumber {
func (x ConnectionFilter) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ConnectionEventType.Descriptor instead.
func (ConnectionEventType) EnumDescriptor() ([]byte, []int) {
// Deprecated: Use ConnectionFilter.Descriptor instead.
func (ConnectionFilter) EnumDescriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{1}
}
type ConnectionSortBy int32
const (
ConnectionSortBy_DATE ConnectionSortBy = 0
ConnectionSortBy_TRAFFIC ConnectionSortBy = 1
ConnectionSortBy_TOTAL_TRAFFIC ConnectionSortBy = 2
)
// Enum value maps for ConnectionSortBy.
var (
ConnectionSortBy_name = map[int32]string{
0: "DATE",
1: "TRAFFIC",
2: "TOTAL_TRAFFIC",
}
ConnectionSortBy_value = map[string]int32{
"DATE": 0,
"TRAFFIC": 1,
"TOTAL_TRAFFIC": 2,
}
)
func (x ConnectionSortBy) Enum() *ConnectionSortBy {
p := new(ConnectionSortBy)
*p = x
return p
}
func (x ConnectionSortBy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ConnectionSortBy) Descriptor() protoreflect.EnumDescriptor {
return file_daemon_started_service_proto_enumTypes[2].Descriptor()
}
func (ConnectionSortBy) Type() protoreflect.EnumType {
return &file_daemon_started_service_proto_enumTypes[2]
}
func (x ConnectionSortBy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ConnectionSortBy.Descriptor instead.
func (ConnectionSortBy) EnumDescriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{2}
}
type ServiceStatus_Type int32
const (
@@ -166,11 +215,11 @@ func (x ServiceStatus_Type) String() string {
}
func (ServiceStatus_Type) Descriptor() protoreflect.EnumDescriptor {
return file_daemon_started_service_proto_enumTypes[2].Descriptor()
return file_daemon_started_service_proto_enumTypes[3].Descriptor()
}
func (ServiceStatus_Type) Type() protoreflect.EnumType {
return &file_daemon_started_service_proto_enumTypes[2]
return &file_daemon_started_service_proto_enumTypes[3]
}
func (x ServiceStatus_Type) Number() protoreflect.EnumNumber {
@@ -1065,6 +1114,8 @@ func (x *SetSystemProxyEnabledRequest) GetEnabled() bool {
type SubscribeConnectionsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"`
Filter ConnectionFilter `protobuf:"varint,2,opt,name=filter,proto3,enum=daemon.ConnectionFilter" json:"filter,omitempty"`
SortBy ConnectionSortBy `protobuf:"varint,3,opt,name=sortBy,proto3,enum=daemon.ConnectionSortBy" json:"sortBy,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -1106,32 +1157,41 @@ func (x *SubscribeConnectionsRequest) GetInterval() int64 {
return 0
}
type ConnectionEvent struct {
func (x *SubscribeConnectionsRequest) GetFilter() ConnectionFilter {
if x != nil {
return x.Filter
}
return ConnectionFilter_ALL
}
func (x *SubscribeConnectionsRequest) GetSortBy() ConnectionSortBy {
if x != nil {
return x.SortBy
}
return ConnectionSortBy_DATE
}
type Connections struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type ConnectionEventType `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.ConnectionEventType" json:"type,omitempty"`
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
Connection *Connection `protobuf:"bytes,3,opt,name=connection,proto3" json:"connection,omitempty"`
UplinkDelta int64 `protobuf:"varint,4,opt,name=uplinkDelta,proto3" json:"uplinkDelta,omitempty"`
DownlinkDelta int64 `protobuf:"varint,5,opt,name=downlinkDelta,proto3" json:"downlinkDelta,omitempty"`
ClosedAt int64 `protobuf:"varint,6,opt,name=closedAt,proto3" json:"closedAt,omitempty"`
Connections []*Connection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ConnectionEvent) Reset() {
*x = ConnectionEvent{}
func (x *Connections) Reset() {
*x = Connections{}
mi := &file_daemon_started_service_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ConnectionEvent) String() string {
func (x *Connections) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectionEvent) ProtoMessage() {}
func (*Connections) ProtoMessage() {}
func (x *ConnectionEvent) ProtoReflect() protoreflect.Message {
func (x *Connections) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1143,105 +1203,18 @@ func (x *ConnectionEvent) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use ConnectionEvent.ProtoReflect.Descriptor instead.
func (*ConnectionEvent) Descriptor() ([]byte, []int) {
// Deprecated: Use Connections.ProtoReflect.Descriptor instead.
func (*Connections) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{17}
}
func (x *ConnectionEvent) GetType() ConnectionEventType {
func (x *Connections) GetConnections() []*Connection {
if x != nil {
return x.Type
}
return ConnectionEventType_CONNECTION_EVENT_NEW
}
func (x *ConnectionEvent) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *ConnectionEvent) GetConnection() *Connection {
if x != nil {
return x.Connection
return x.Connections
}
return nil
}
func (x *ConnectionEvent) GetUplinkDelta() int64 {
if x != nil {
return x.UplinkDelta
}
return 0
}
func (x *ConnectionEvent) GetDownlinkDelta() int64 {
if x != nil {
return x.DownlinkDelta
}
return 0
}
func (x *ConnectionEvent) GetClosedAt() int64 {
if x != nil {
return x.ClosedAt
}
return 0
}
type ConnectionEvents struct {
state protoimpl.MessageState `protogen:"open.v1"`
Events []*ConnectionEvent `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
Reset_ bool `protobuf:"varint,2,opt,name=reset,proto3" json:"reset,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ConnectionEvents) Reset() {
*x = ConnectionEvents{}
mi := &file_daemon_started_service_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ConnectionEvents) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConnectionEvents) ProtoMessage() {}
func (x *ConnectionEvents) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[18]
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 ConnectionEvents.ProtoReflect.Descriptor instead.
func (*ConnectionEvents) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{18}
}
func (x *ConnectionEvents) GetEvents() []*ConnectionEvent {
if x != nil {
return x.Events
}
return nil
}
func (x *ConnectionEvents) GetReset_() bool {
if x != nil {
return x.Reset_
}
return false
}
type Connection struct {
state protoimpl.MessageState `protogen:"open.v1"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
@@ -1272,7 +1245,7 @@ type Connection struct {
func (x *Connection) Reset() {
*x = Connection{}
mi := &file_daemon_started_service_proto_msgTypes[19]
mi := &file_daemon_started_service_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1284,7 +1257,7 @@ func (x *Connection) String() string {
func (*Connection) ProtoMessage() {}
func (x *Connection) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[19]
mi := &file_daemon_started_service_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1297,7 +1270,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message {
// Deprecated: Use Connection.ProtoReflect.Descriptor instead.
func (*Connection) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{19}
return file_daemon_started_service_proto_rawDescGZIP(), []int{18}
}
func (x *Connection) GetId() string {
@@ -1460,14 +1433,14 @@ type ProcessInfo struct {
UserId int32 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"`
UserName string `protobuf:"bytes,3,opt,name=userName,proto3" json:"userName,omitempty"`
ProcessPath string `protobuf:"bytes,4,opt,name=processPath,proto3" json:"processPath,omitempty"`
PackageNames []string `protobuf:"bytes,5,rep,name=packageNames,proto3" json:"packageNames,omitempty"`
PackageName string `protobuf:"bytes,5,opt,name=packageName,proto3" json:"packageName,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ProcessInfo) Reset() {
*x = ProcessInfo{}
mi := &file_daemon_started_service_proto_msgTypes[20]
mi := &file_daemon_started_service_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1479,7 +1452,7 @@ func (x *ProcessInfo) String() string {
func (*ProcessInfo) ProtoMessage() {}
func (x *ProcessInfo) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[20]
mi := &file_daemon_started_service_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1492,7 +1465,7 @@ func (x *ProcessInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead.
func (*ProcessInfo) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{20}
return file_daemon_started_service_proto_rawDescGZIP(), []int{19}
}
func (x *ProcessInfo) GetProcessId() uint32 {
@@ -1523,11 +1496,11 @@ func (x *ProcessInfo) GetProcessPath() string {
return ""
}
func (x *ProcessInfo) GetPackageNames() []string {
func (x *ProcessInfo) GetPackageName() string {
if x != nil {
return x.PackageNames
return x.PackageName
}
return nil
return ""
}
type CloseConnectionRequest struct {
@@ -1539,7 +1512,7 @@ type CloseConnectionRequest struct {
func (x *CloseConnectionRequest) Reset() {
*x = CloseConnectionRequest{}
mi := &file_daemon_started_service_proto_msgTypes[21]
mi := &file_daemon_started_service_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1551,7 +1524,7 @@ func (x *CloseConnectionRequest) String() string {
func (*CloseConnectionRequest) ProtoMessage() {}
func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[21]
mi := &file_daemon_started_service_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1564,7 +1537,7 @@ func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead.
func (*CloseConnectionRequest) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{21}
return file_daemon_started_service_proto_rawDescGZIP(), []int{20}
}
func (x *CloseConnectionRequest) GetId() string {
@@ -1583,7 +1556,7 @@ type DeprecatedWarnings struct {
func (x *DeprecatedWarnings) Reset() {
*x = DeprecatedWarnings{}
mi := &file_daemon_started_service_proto_msgTypes[22]
mi := &file_daemon_started_service_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1595,7 +1568,7 @@ func (x *DeprecatedWarnings) String() string {
func (*DeprecatedWarnings) ProtoMessage() {}
func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[22]
mi := &file_daemon_started_service_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1608,7 +1581,7 @@ func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead.
func (*DeprecatedWarnings) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{22}
return file_daemon_started_service_proto_rawDescGZIP(), []int{21}
}
func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning {
@@ -1629,7 +1602,7 @@ type DeprecatedWarning struct {
func (x *DeprecatedWarning) Reset() {
*x = DeprecatedWarning{}
mi := &file_daemon_started_service_proto_msgTypes[23]
mi := &file_daemon_started_service_proto_msgTypes[22]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1641,7 +1614,7 @@ func (x *DeprecatedWarning) String() string {
func (*DeprecatedWarning) ProtoMessage() {}
func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[23]
mi := &file_daemon_started_service_proto_msgTypes[22]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1654,7 +1627,7 @@ func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {
// Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead.
func (*DeprecatedWarning) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{23}
return file_daemon_started_service_proto_rawDescGZIP(), []int{22}
}
func (x *DeprecatedWarning) GetMessage() string {
@@ -1687,7 +1660,7 @@ type StartedAt struct {
func (x *StartedAt) Reset() {
*x = StartedAt{}
mi := &file_daemon_started_service_proto_msgTypes[24]
mi := &file_daemon_started_service_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1699,7 +1672,7 @@ func (x *StartedAt) String() string {
func (*StartedAt) ProtoMessage() {}
func (x *StartedAt) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[24]
mi := &file_daemon_started_service_proto_msgTypes[23]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1712,7 +1685,7 @@ func (x *StartedAt) ProtoReflect() protoreflect.Message {
// Deprecated: Use StartedAt.ProtoReflect.Descriptor instead.
func (*StartedAt) Descriptor() ([]byte, []int) {
return file_daemon_started_service_proto_rawDescGZIP(), []int{24}
return file_daemon_started_service_proto_rawDescGZIP(), []int{23}
}
func (x *StartedAt) GetStartedAt() int64 {
@@ -1732,7 +1705,7 @@ type Log_Message struct {
func (x *Log_Message) Reset() {
*x = Log_Message{}
mi := &file_daemon_started_service_proto_msgTypes[25]
mi := &file_daemon_started_service_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -1744,7 +1717,7 @@ func (x *Log_Message) String() string {
func (*Log_Message) ProtoMessage() {}
func (x *Log_Message) ProtoReflect() protoreflect.Message {
mi := &file_daemon_started_service_proto_msgTypes[25]
mi := &file_daemon_started_service_proto_msgTypes[24]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1845,21 +1818,13 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" +
"\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" +
"\x1cSetSystemProxyEnabledRequest\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\"9\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\"\x9d\x01\n" +
"\x1bSubscribeConnectionsRequest\x12\x1a\n" +
"\binterval\x18\x01 \x01(\x03R\binterval\"\xea\x01\n" +
"\x0fConnectionEvent\x12/\n" +
"\x04type\x18\x01 \x01(\x0e2\x1b.daemon.ConnectionEventTypeR\x04type\x12\x0e\n" +
"\x02id\x18\x02 \x01(\tR\x02id\x122\n" +
"\n" +
"connection\x18\x03 \x01(\v2\x12.daemon.ConnectionR\n" +
"connection\x12 \n" +
"\vuplinkDelta\x18\x04 \x01(\x03R\vuplinkDelta\x12$\n" +
"\rdownlinkDelta\x18\x05 \x01(\x03R\rdownlinkDelta\x12\x1a\n" +
"\bclosedAt\x18\x06 \x01(\x03R\bclosedAt\"Y\n" +
"\x10ConnectionEvents\x12/\n" +
"\x06events\x18\x01 \x03(\v2\x17.daemon.ConnectionEventR\x06events\x12\x14\n" +
"\x05reset\x18\x02 \x01(\bR\x05reset\"\x95\x05\n" +
"\binterval\x18\x01 \x01(\x03R\binterval\x120\n" +
"\x06filter\x18\x02 \x01(\x0e2\x18.daemon.ConnectionFilterR\x06filter\x120\n" +
"\x06sortBy\x18\x03 \x01(\x0e2\x18.daemon.ConnectionSortByR\x06sortBy\"C\n" +
"\vConnections\x124\n" +
"\vconnections\x18\x01 \x03(\v2\x12.daemon.ConnectionR\vconnections\"\x95\x05\n" +
"\n" +
"Connection\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" +
@@ -1884,13 +1849,13 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\boutbound\x18\x13 \x01(\tR\boutbound\x12\"\n" +
"\foutboundType\x18\x14 \x01(\tR\foutboundType\x12\x1c\n" +
"\tchainList\x18\x15 \x03(\tR\tchainList\x125\n" +
"\vprocessInfo\x18\x16 \x01(\v2\x13.daemon.ProcessInfoR\vprocessInfo\"\xa5\x01\n" +
"\vprocessInfo\x18\x16 \x01(\v2\x13.daemon.ProcessInfoR\vprocessInfo\"\xa3\x01\n" +
"\vProcessInfo\x12\x1c\n" +
"\tprocessId\x18\x01 \x01(\rR\tprocessId\x12\x16\n" +
"\x06userId\x18\x02 \x01(\x05R\x06userId\x12\x1a\n" +
"\buserName\x18\x03 \x01(\tR\buserName\x12 \n" +
"\vprocessPath\x18\x04 \x01(\tR\vprocessPath\x12\"\n" +
"\fpackageNames\x18\x05 \x03(\tR\fpackageNames\"(\n" +
"\vprocessPath\x18\x04 \x01(\tR\vprocessPath\x12 \n" +
"\vpackageName\x18\x05 \x01(\tR\vpackageName\"(\n" +
"\x16CloseConnectionRequest\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\"K\n" +
"\x12DeprecatedWarnings\x125\n" +
@@ -1908,11 +1873,17 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\x04WARN\x10\x03\x12\b\n" +
"\x04INFO\x10\x04\x12\t\n" +
"\x05DEBUG\x10\x05\x12\t\n" +
"\x05TRACE\x10\x06*i\n" +
"\x13ConnectionEventType\x12\x18\n" +
"\x14CONNECTION_EVENT_NEW\x10\x00\x12\x1b\n" +
"\x17CONNECTION_EVENT_UPDATE\x10\x01\x12\x1b\n" +
"\x17CONNECTION_EVENT_CLOSED\x10\x022\xe5\v\n" +
"\x05TRACE\x10\x06*3\n" +
"\x10ConnectionFilter\x12\a\n" +
"\x03ALL\x10\x00\x12\n" +
"\n" +
"\x06ACTIVE\x10\x01\x12\n" +
"\n" +
"\x06CLOSED\x10\x02*<\n" +
"\x10ConnectionSortBy\x12\b\n" +
"\x04DATE\x10\x00\x12\v\n" +
"\aTRAFFIC\x10\x01\x12\x11\n" +
"\rTOTAL_TRAFFIC\x10\x022\xe0\v\n" +
"\x0eStartedService\x12=\n" +
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
@@ -1929,8 +1900,8 @@ const file_daemon_started_service_proto_rawDesc = "" +
"\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" +
"\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" +
"\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" +
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" +
"\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x18.daemon.ConnectionEvents\"\x000\x01\x12K\n" +
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12T\n" +
"\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x13.daemon.Connections\"\x000\x01\x12K\n" +
"\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" +
"\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" +
"\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12;\n" +
@@ -1949,31 +1920,31 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte {
}
var (
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
file_daemon_started_service_proto_goTypes = []any{
(LogLevel)(0), // 0: daemon.LogLevel
(ConnectionEventType)(0), // 1: daemon.ConnectionEventType
(ServiceStatus_Type)(0), // 2: daemon.ServiceStatus.Type
(*ServiceStatus)(nil), // 3: daemon.ServiceStatus
(*ReloadServiceRequest)(nil), // 4: daemon.ReloadServiceRequest
(*SubscribeStatusRequest)(nil), // 5: daemon.SubscribeStatusRequest
(*Log)(nil), // 6: daemon.Log
(*DefaultLogLevel)(nil), // 7: daemon.DefaultLogLevel
(*Status)(nil), // 8: daemon.Status
(*Groups)(nil), // 9: daemon.Groups
(*Group)(nil), // 10: daemon.Group
(*GroupItem)(nil), // 11: daemon.GroupItem
(*URLTestRequest)(nil), // 12: daemon.URLTestRequest
(*SelectOutboundRequest)(nil), // 13: daemon.SelectOutboundRequest
(*SetGroupExpandRequest)(nil), // 14: daemon.SetGroupExpandRequest
(*ClashMode)(nil), // 15: daemon.ClashMode
(*ClashModeStatus)(nil), // 16: daemon.ClashModeStatus
(*SystemProxyStatus)(nil), // 17: daemon.SystemProxyStatus
(*SetSystemProxyEnabledRequest)(nil), // 18: daemon.SetSystemProxyEnabledRequest
(*SubscribeConnectionsRequest)(nil), // 19: daemon.SubscribeConnectionsRequest
(*ConnectionEvent)(nil), // 20: daemon.ConnectionEvent
(*ConnectionEvents)(nil), // 21: daemon.ConnectionEvents
(ConnectionFilter)(0), // 1: daemon.ConnectionFilter
(ConnectionSortBy)(0), // 2: daemon.ConnectionSortBy
(ServiceStatus_Type)(0), // 3: daemon.ServiceStatus.Type
(*ServiceStatus)(nil), // 4: daemon.ServiceStatus
(*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest
(*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest
(*Log)(nil), // 7: daemon.Log
(*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel
(*Status)(nil), // 9: daemon.Status
(*Groups)(nil), // 10: daemon.Groups
(*Group)(nil), // 11: daemon.Group
(*GroupItem)(nil), // 12: daemon.GroupItem
(*URLTestRequest)(nil), // 13: daemon.URLTestRequest
(*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest
(*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest
(*ClashMode)(nil), // 16: daemon.ClashMode
(*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus
(*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus
(*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest
(*SubscribeConnectionsRequest)(nil), // 20: daemon.SubscribeConnectionsRequest
(*Connections)(nil), // 21: daemon.Connections
(*Connection)(nil), // 22: daemon.Connection
(*ProcessInfo)(nil), // 23: daemon.ProcessInfo
(*CloseConnectionRequest)(nil), // 24: daemon.CloseConnectionRequest
@@ -1986,14 +1957,14 @@ var (
)
var file_daemon_started_service_proto_depIdxs = []int32{
2, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type
3, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type
28, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel
10, // 3: daemon.Groups.group:type_name -> daemon.Group
11, // 4: daemon.Group.items:type_name -> daemon.GroupItem
1, // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType
22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection
20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent
11, // 3: daemon.Groups.group:type_name -> daemon.Group
12, // 4: daemon.Group.items:type_name -> daemon.GroupItem
1, // 5: daemon.SubscribeConnectionsRequest.filter:type_name -> daemon.ConnectionFilter
2, // 6: daemon.SubscribeConnectionsRequest.sortBy:type_name -> daemon.ConnectionSortBy
22, // 7: daemon.Connections.connections:type_name -> daemon.Connection
23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo
26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning
0, // 10: daemon.Log.Message.level:type_name -> daemon.LogLevel
@@ -2003,38 +1974,38 @@ var file_daemon_started_service_proto_depIdxs = []int32{
29, // 14: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
29, // 15: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
29, // 16: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
5, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
6, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
29, // 18: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
29, // 19: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
29, // 20: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
15, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
12, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
13, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
14, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
16, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
13, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
14, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
15, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
29, // 25: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
18, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
19, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
19, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
20, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
24, // 28: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
29, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
29, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
3, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
6, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
7, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
4, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
7, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
8, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
29, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
8, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
9, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
16, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
15, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
9, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
10, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
17, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
16, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
29, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
29, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
29, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
29, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
17, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
18, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
29, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.Connections
29, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
29, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
25, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
@@ -2056,8 +2027,8 @@ func file_daemon_started_service_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)),
NumEnums: 3,
NumMessages: 26,
NumEnums: 4,
NumMessages: 25,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -27,7 +27,7 @@ service StartedService {
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream Connections) {}
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
@@ -143,26 +143,24 @@ message SetSystemProxyEnabledRequest {
message SubscribeConnectionsRequest {
int64 interval = 1;
ConnectionFilter filter = 2;
ConnectionSortBy sortBy = 3;
}
enum ConnectionEventType {
CONNECTION_EVENT_NEW = 0;
CONNECTION_EVENT_UPDATE = 1;
CONNECTION_EVENT_CLOSED = 2;
enum ConnectionFilter {
ALL = 0;
ACTIVE = 1;
CLOSED = 2;
}
message ConnectionEvent {
ConnectionEventType type = 1;
string id = 2;
Connection connection = 3;
int64 uplinkDelta = 4;
int64 downlinkDelta = 5;
int64 closedAt = 6;
enum ConnectionSortBy {
DATE = 0;
TRAFFIC = 1;
TOTAL_TRAFFIC = 2;
}
message ConnectionEvents {
repeated ConnectionEvent events = 1;
bool reset = 2;
message Connections {
repeated Connection connections = 1;
}
message Connection {
@@ -195,7 +193,7 @@ message ProcessInfo {
int32 userId = 2;
string userName = 3;
string processPath = 4;
repeated string packageNames = 5;
string packageName = 5;
}
message CloseConnectionRequest {

View File

@@ -58,7 +58,7 @@ type StartedServiceClient interface {
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error)
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
@@ -278,13 +278,13 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se
return out, nil
}
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Connections], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, ConnectionEvents]{ClientStream: stream}
x := &grpc.GenericClientStream[SubscribeConnectionsRequest, Connections]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@@ -295,7 +295,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.
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) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
@@ -357,7 +357,7 @@ type StartedServiceServer interface {
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
@@ -373,87 +373,87 @@ type StartedServiceServer interface {
type UnimplementedStartedServiceServer struct{}
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) {
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 {
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 {
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) {
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) {
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 {
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 {
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) {
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 {
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) {
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) {
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) {
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) {
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) {
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) {
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
return nil, status.Errorf(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
}
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[Connections]) error {
return status.Errorf(codes.Unimplemented, "method SubscribeConnections not implemented")
}
func (UnimplementedStartedServiceServer) CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error) {
return nil, status.Error(codes.Unimplemented, "method CloseConnection not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CloseConnection not implemented")
}
func (UnimplementedStartedServiceServer) CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
return nil, status.Error(codes.Unimplemented, "method CloseAllConnections not implemented")
return nil, status.Errorf(codes.Unimplemented, "method CloseAllConnections not implemented")
}
func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error) {
return nil, status.Error(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetDeprecatedWarnings not implemented")
}
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
return nil, status.Errorf(codes.Unimplemented, "method GetStartedAt not implemented")
}
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
@@ -466,7 +466,7 @@ type UnsafeStartedServiceServer interface {
}
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
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@@ -734,11 +734,11 @@ func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.S
if err := stream.RecvMsg(m); err != nil {
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.
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) {
in := new(CloseConnectionRequest)

View File

@@ -3,11 +3,11 @@ package box
import (
"runtime/debug"
"github.com/sagernet/sing-box/common/conntrack"
"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)
if options.GCPercent != nil {
debug.SetGCPercent(*options.GCPercent)
@@ -26,9 +26,9 @@ func applyDebugOptions(options option.DebugOptions) error {
}
if options.MemoryLimit.Value() != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
conntrack.MemoryLimit = options.MemoryLimit.Value()
}
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
}

View File

@@ -144,11 +144,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if c.cache != nil {
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
select {
case <-cond:
case <-ctx.Done():
return nil, ctx.Err()
}
<-cond
} else {
defer func() {
c.cacheLock.Delete(question)
@@ -158,11 +154,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
} else if c.transportCache != nil {
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
select {
case <-cond:
case <-ctx.Done():
return nil, ctx.Err()
}
<-cond
} else {
defer func() {
c.transportCacheLock.Delete(question)
@@ -240,10 +232,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if responseChecker != nil {
var rejected bool
// TODO: add accept_any rule and support to check response instead of addresses
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
rejected = true
} else if len(response.Answer) == 0 {
rejected = !responseChecker(nil)
} else {
rejected = !responseChecker(MessageToAddresses(response))
}
@@ -283,9 +273,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if timeToLive == 0 {
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
}
@@ -297,9 +284,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
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
}
}
@@ -330,20 +314,16 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
} else {
strategy = options.Strategy
}
lookupOptions := options
if options.LookupStrategy != C.DomainStrategyAsIS {
lookupOptions.Strategy = strategy
}
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 {
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 response6 []netip.Addr
var group task.Group
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 {
return err
}
@@ -351,7 +331,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
return nil
})
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 {
return err
}
@@ -387,21 +367,21 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
}
if c.disableExpire {
if !c.independentCache {
c.cache.Add(question, message.Copy())
c.cache.Add(question, message)
} else {
c.transportCache.Add(transportCacheKey{
Question: question,
transportTag: transport.Tag(),
}, message.Copy())
}, message)
}
} else {
if !c.independentCache {
c.cache.AddWithLifetime(question, message.Copy(), time.Second*time.Duration(timeToLive))
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
} else {
c.transportCache.AddWithLifetime(transportCacheKey{
Question: question,
transportTag: transport.Tag(),
}, message.Copy(), time.Second*time.Duration(timeToLive))
}, message, time.Second*time.Duration(timeToLive))
}
}
}
@@ -492,9 +472,6 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
var originTTL int
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList {
if record.Header().Rrtype == dns.TypeOPT {
continue
}
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
originTTL = int(record.Header().Ttl)
}
@@ -509,18 +486,12 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
duration := uint32(originTTL - nowTTL)
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 = record.Header().Ttl - duration
}
}
} else {
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 = uint32(nowTTL)
}
}

View File

@@ -195,16 +195,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
}
}
}
transport := r.transport.Default()
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
if options.Strategy == C.DomainStrategyAsIS {
options.Strategy = legacyTransport.LegacyStrategy()
}
if !options.ClientSubnet.IsValid() {
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
}
}
return transport, nil, -1
return r.transport.Default(), nil, -1
}
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
@@ -281,7 +272,13 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
return action.Response(message), nil
}
}
responseCheck := addressLimitResponseCheck(rule, metadata)
var responseCheck func(responseAddrs []netip.Addr) bool
if rule != nil && rule.WithAddressLimit() {
responseCheck = func(responseAddrs []netip.Addr) bool {
metadata.DestinationAddresses = responseAddrs
return rule.MatchAddressLimit(metadata)
}
}
if dnsOptions.Strategy == C.DomainStrategyAsIS {
dnsOptions.Strategy = r.defaultDomainStrategy
}
@@ -354,7 +351,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
transport := options.Transport
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
if options.Strategy == C.DomainStrategyAsIS {
options.Strategy = legacyTransport.LegacyStrategy()
options.Strategy = r.defaultDomainStrategy
}
if !options.ClientSubnet.IsValid() {
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
@@ -380,11 +377,9 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
case *R.RuleActionReject:
return nil, &R.RejectedError{Cause: action.Error(ctx)}
case *R.RuleActionPredefined:
responseAddrs = nil
if action.Rcode != mDNS.RcodeSuccess {
err = RcodeError(action.Rcode)
} else {
err = nil
for _, answer := range action.Answer {
switch record := answer.(type) {
case *mDNS.A:
@@ -397,7 +392,13 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
goto response
}
}
responseCheck := addressLimitResponseCheck(rule, metadata)
var responseCheck func(responseAddrs []netip.Addr) bool
if rule != nil && rule.WithAddressLimit() {
responseCheck = func(responseAddrs []netip.Addr) bool {
metadata.DestinationAddresses = responseAddrs
return rule.MatchAddressLimit(metadata)
}
}
if dnsOptions.Strategy == C.DomainStrategyAsIS {
dnsOptions.Strategy = r.defaultDomainStrategy
}
@@ -425,18 +426,6 @@ func isAddressQuery(message *mDNS.Msg) bool {
return false
}
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool {
if rule == nil || !rule.WithAddressLimit() {
return nil
}
responseMetadata := *metadata
return func(responseAddrs []netip.Addr) bool {
checkMetadata := responseMetadata
checkMetadata.DestinationAddresses = responseAddrs
return rule.MatchAddressLimit(&checkMetadata)
}
}
func (r *Router) ClearCache() {
r.client.ClearCache()
if r.platformInterface != nil {

View File

@@ -4,9 +4,6 @@ import (
"context"
"net"
"sync"
"time"
E "github.com/sagernet/sing/common/exceptions"
)
type ConnectorCallbacks[T any] struct {
@@ -19,11 +16,10 @@ type Connector[T any] struct {
dial func(ctx context.Context) (T, error)
callbacks ConnectorCallbacks[T]
access sync.Mutex
connection T
hasConnection bool
connectionCancel context.CancelFunc
connecting chan struct{}
access sync.Mutex
connection T
hasConnection bool
connecting chan struct{}
closeCtx context.Context
closed bool
@@ -51,16 +47,6 @@ func NewSingleflightConnector(closeCtx context.Context, dial func(context.Contex
})
}
type contextKeyConnecting struct{}
var errRecursiveConnectorDial = E.New("recursive connector dial")
type connectorDialResult[T any] struct {
connection T
cancel context.CancelFunc
err error
}
func (c *Connector[T]) Get(ctx context.Context) (T, error) {
var zero T
for {
@@ -78,14 +64,6 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) {
}
c.hasConnection = false
if c.connectionCancel != nil {
c.connectionCancel()
c.connectionCancel = nil
}
if isRecursiveConnectorDial(ctx, c) {
c.access.Unlock()
return zero, errRecursiveConnectorDial
}
if c.connecting != nil {
connecting := c.connecting
@@ -101,134 +79,48 @@ func (c *Connector[T]) Get(ctx context.Context) (T, error) {
}
}
if err := ctx.Err(); err != nil {
c.connecting = make(chan struct{})
c.access.Unlock()
connection, err := c.dialWithCancellation(ctx)
c.access.Lock()
close(c.connecting)
c.connecting = nil
if err != nil {
c.access.Unlock()
return zero, err
}
connecting := make(chan struct{})
c.connecting = connecting
dialContext := context.WithValue(ctx, contextKeyConnecting{}, c)
dialResult := make(chan connectorDialResult[T], 1)
c.access.Unlock()
go func() {
connection, cancel, err := c.dialWithCancellation(dialContext)
dialResult <- connectorDialResult[T]{
connection: connection,
cancel: cancel,
err: err,
}
}()
select {
case result := <-dialResult:
return c.completeDial(ctx, connecting, result)
case <-ctx.Done():
go func() {
result := <-dialResult
_, _ = c.completeDial(ctx, connecting, result)
}()
return zero, ctx.Err()
case <-c.closeCtx.Done():
go func() {
result := <-dialResult
_, _ = c.completeDial(ctx, connecting, result)
}()
if c.closed {
c.callbacks.Close(connection)
c.access.Unlock()
return zero, ErrTransportClosed
}
c.connection = connection
c.hasConnection = true
result := c.connection
c.access.Unlock()
return result, nil
}
}
func isRecursiveConnectorDial[T any](ctx context.Context, connector *Connector[T]) bool {
dialConnector, loaded := ctx.Value(contextKeyConnecting{}).(*Connector[T])
return loaded && dialConnector == connector
}
func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, error) {
dialCtx, cancel := context.WithCancel(ctx)
defer cancel()
func (c *Connector[T]) completeDial(ctx context.Context, connecting chan struct{}, result connectorDialResult[T]) (T, error) {
var zero T
c.access.Lock()
defer c.access.Unlock()
defer func() {
if c.connecting == connecting {
c.connecting = nil
go func() {
select {
case <-c.closeCtx.Done():
cancel()
case <-dialCtx.Done():
}
close(connecting)
}()
if result.err != nil {
return zero, result.err
}
if c.closed || c.closeCtx.Err() != nil {
result.cancel()
c.callbacks.Close(result.connection)
return zero, ErrTransportClosed
}
if err := ctx.Err(); err != nil {
result.cancel()
c.callbacks.Close(result.connection)
return zero, err
}
c.connection = result.connection
c.hasConnection = true
c.connectionCancel = result.cancel
return c.connection, nil
}
func (c *Connector[T]) dialWithCancellation(ctx context.Context) (T, context.CancelFunc, error) {
var zero T
if err := ctx.Err(); err != nil {
return zero, nil, err
}
connCtx, cancel := context.WithCancel(c.closeCtx)
var (
stateAccess sync.Mutex
dialComplete bool
)
stopCancel := context.AfterFunc(ctx, func() {
stateAccess.Lock()
if !dialComplete {
cancel()
}
stateAccess.Unlock()
})
select {
case <-ctx.Done():
stateAccess.Lock()
dialComplete = true
stateAccess.Unlock()
stopCancel()
cancel()
return zero, nil, ctx.Err()
default:
}
connection, err := c.dial(valueContext{connCtx, ctx})
stateAccess.Lock()
dialComplete = true
stateAccess.Unlock()
stopCancel()
if err != nil {
cancel()
return zero, nil, err
}
return connection, cancel, nil
}
type valueContext struct {
context.Context
parent context.Context
}
func (v valueContext) Value(key any) any {
return v.parent.Value(key)
}
func (v valueContext) Deadline() (time.Time, bool) {
return v.parent.Deadline()
return c.dial(dialCtx)
}
func (c *Connector[T]) Close() error {
@@ -240,10 +132,6 @@ func (c *Connector[T]) Close() error {
}
c.closed = true
if c.connectionCancel != nil {
c.connectionCancel()
c.connectionCancel = nil
}
if c.hasConnection {
c.callbacks.Close(c.connection)
c.hasConnection = false
@@ -256,10 +144,6 @@ func (c *Connector[T]) Reset() {
c.access.Lock()
defer c.access.Unlock()
if c.connectionCancel != nil {
c.connectionCancel()
c.connectionCancel = nil
}
if c.hasConnection {
c.callbacks.Reset(c.connection)
c.hasConnection = false

View File

@@ -1,407 +0,0 @@
package transport
import (
"context"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type testConnectorConnection struct{}
func TestConnectorRecursiveGetFailsFast(t *testing.T) {
t.Parallel()
var (
dialCount atomic.Int32
closeCount atomic.Int32
connector *Connector[*testConnectorConnection]
)
dial := func(ctx context.Context) (*testConnectorConnection, error) {
dialCount.Add(1)
_, err := connector.Get(ctx)
if err != nil {
return nil, err
}
return &testConnectorConnection{}, nil
}
connector = NewConnector(context.Background(), dial, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {
closeCount.Add(1)
},
Reset: func(connection *testConnectorConnection) {
closeCount.Add(1)
},
})
_, err := connector.Get(context.Background())
require.ErrorIs(t, err, errRecursiveConnectorDial)
require.EqualValues(t, 1, dialCount.Load())
require.EqualValues(t, 0, closeCount.Load())
}
func TestConnectorRecursiveGetAcrossConnectorsAllowed(t *testing.T) {
t.Parallel()
var (
outerDialCount atomic.Int32
innerDialCount atomic.Int32
outerConnector *Connector[*testConnectorConnection]
innerConnector *Connector[*testConnectorConnection]
)
innerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
innerDialCount.Add(1)
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {},
Reset: func(connection *testConnectorConnection) {},
})
outerConnector = NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
outerDialCount.Add(1)
_, err := innerConnector.Get(ctx)
if err != nil {
return nil, err
}
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {},
Reset: func(connection *testConnectorConnection) {},
})
_, err := outerConnector.Get(context.Background())
require.NoError(t, err)
require.EqualValues(t, 1, outerDialCount.Load())
require.EqualValues(t, 1, innerDialCount.Load())
}
func TestConnectorDialContextPreservesValueAndDeadline(t *testing.T) {
t.Parallel()
type contextKey struct{}
var (
dialValue any
dialDeadline time.Time
dialHasDeadline bool
)
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
dialValue = ctx.Value(contextKey{})
dialDeadline, dialHasDeadline = ctx.Deadline()
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {},
Reset: func(connection *testConnectorConnection) {},
})
deadline := time.Now().Add(time.Minute)
requestContext, cancel := context.WithDeadline(context.WithValue(context.Background(), contextKey{}, "test-value"), deadline)
defer cancel()
_, err := connector.Get(requestContext)
require.NoError(t, err)
require.Equal(t, "test-value", dialValue)
require.True(t, dialHasDeadline)
require.WithinDuration(t, deadline, dialDeadline, time.Second)
}
func TestConnectorDialSkipsCanceledRequest(t *testing.T) {
t.Parallel()
var dialCount atomic.Int32
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
dialCount.Add(1)
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {},
Reset: func(connection *testConnectorConnection) {},
})
requestContext, cancel := context.WithCancel(context.Background())
cancel()
_, err := connector.Get(requestContext)
require.ErrorIs(t, err, context.Canceled)
require.EqualValues(t, 0, dialCount.Load())
}
func TestConnectorCanceledRequestDoesNotCacheConnection(t *testing.T) {
t.Parallel()
var (
dialCount atomic.Int32
closeCount atomic.Int32
)
dialStarted := make(chan struct{}, 1)
releaseDial := make(chan struct{})
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
dialCount.Add(1)
select {
case dialStarted <- struct{}{}:
default:
}
<-releaseDial
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {
closeCount.Add(1)
},
Reset: func(connection *testConnectorConnection) {},
})
requestContext, cancel := context.WithCancel(context.Background())
result := make(chan error, 1)
go func() {
_, err := connector.Get(requestContext)
result <- err
}()
<-dialStarted
cancel()
close(releaseDial)
err := <-result
require.ErrorIs(t, err, context.Canceled)
require.EqualValues(t, 1, dialCount.Load())
require.Eventually(t, func() bool {
return closeCount.Load() == 1
}, time.Second, 10*time.Millisecond)
_, err = connector.Get(context.Background())
require.NoError(t, err)
require.EqualValues(t, 2, dialCount.Load())
}
func TestConnectorCanceledRequestReturnsBeforeIgnoredDialCompletes(t *testing.T) {
t.Parallel()
var (
dialCount atomic.Int32
closeCount atomic.Int32
)
dialStarted := make(chan struct{}, 1)
releaseDial := make(chan struct{})
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
dialCount.Add(1)
select {
case dialStarted <- struct{}{}:
default:
}
<-releaseDial
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {
closeCount.Add(1)
},
Reset: func(connection *testConnectorConnection) {},
})
requestContext, cancel := context.WithCancel(context.Background())
result := make(chan error, 1)
go func() {
_, err := connector.Get(requestContext)
result <- err
}()
<-dialStarted
cancel()
select {
case err := <-result:
require.ErrorIs(t, err, context.Canceled)
case <-time.After(time.Second):
t.Fatal("Get did not return after request cancel")
}
require.EqualValues(t, 1, dialCount.Load())
require.EqualValues(t, 0, closeCount.Load())
close(releaseDial)
require.Eventually(t, func() bool {
return closeCount.Load() == 1
}, time.Second, 10*time.Millisecond)
_, err := connector.Get(context.Background())
require.NoError(t, err)
require.EqualValues(t, 2, dialCount.Load())
}
func TestConnectorWaiterDoesNotStartNewDialBeforeCanceledDialCompletes(t *testing.T) {
t.Parallel()
var (
dialCount atomic.Int32
closeCount atomic.Int32
)
firstDialStarted := make(chan struct{}, 1)
secondDialStarted := make(chan struct{}, 1)
releaseFirstDial := make(chan struct{})
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
attempt := dialCount.Add(1)
switch attempt {
case 1:
select {
case firstDialStarted <- struct{}{}:
default:
}
<-releaseFirstDial
case 2:
select {
case secondDialStarted <- struct{}{}:
default:
}
}
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {
closeCount.Add(1)
},
Reset: func(connection *testConnectorConnection) {},
})
requestContext, cancel := context.WithCancel(context.Background())
firstResult := make(chan error, 1)
go func() {
_, err := connector.Get(requestContext)
firstResult <- err
}()
<-firstDialStarted
cancel()
secondResult := make(chan error, 1)
go func() {
_, err := connector.Get(context.Background())
secondResult <- err
}()
select {
case <-secondDialStarted:
t.Fatal("second dial started before first dial completed")
case <-time.After(100 * time.Millisecond):
}
select {
case err := <-firstResult:
require.ErrorIs(t, err, context.Canceled)
case <-time.After(time.Second):
t.Fatal("first Get did not return after request cancel")
}
close(releaseFirstDial)
require.Eventually(t, func() bool {
return closeCount.Load() == 1
}, time.Second, 10*time.Millisecond)
select {
case <-secondDialStarted:
case <-time.After(time.Second):
t.Fatal("second dial did not start after first dial completed")
}
err := <-secondResult
require.NoError(t, err)
require.EqualValues(t, 2, dialCount.Load())
}
func TestConnectorDialContextNotCanceledByRequestContextAfterDial(t *testing.T) {
t.Parallel()
var dialContext context.Context
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
dialContext = ctx
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {},
Reset: func(connection *testConnectorConnection) {},
})
requestContext, cancel := context.WithCancel(context.Background())
_, err := connector.Get(requestContext)
require.NoError(t, err)
require.NotNil(t, dialContext)
cancel()
select {
case <-dialContext.Done():
t.Fatal("dial context canceled by request context after successful dial")
case <-time.After(100 * time.Millisecond):
}
err = connector.Close()
require.NoError(t, err)
}
func TestConnectorDialContextCanceledOnClose(t *testing.T) {
t.Parallel()
var dialContext context.Context
connector := NewConnector(context.Background(), func(ctx context.Context) (*testConnectorConnection, error) {
dialContext = ctx
return &testConnectorConnection{}, nil
}, ConnectorCallbacks[*testConnectorConnection]{
IsClosed: func(connection *testConnectorConnection) bool {
return false
},
Close: func(connection *testConnectorConnection) {},
Reset: func(connection *testConnectorConnection) {},
})
_, err := connector.Get(context.Background())
require.NoError(t, err)
require.NotNil(t, dialContext)
select {
case <-dialContext.Done():
t.Fatal("dial context canceled before connector close")
default:
}
err = connector.Close()
require.NoError(t, err)
select {
case <-dialContext.Done():
case <-time.After(time.Second):
t.Fatal("dial context not canceled after connector close")
}
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"syscall"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
@@ -39,6 +40,13 @@ func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr,
results := make(chan queryResult)
startRacer := func(ctx context.Context, fqdn string) {
response, err := t.tryOneName(ctx, servers, fqdn, message)
if err == nil {
if response.Rcode != mDNS.RcodeSuccess {
err = dns.RcodeError(response.Rcode)
} else if len(dns.MessageToAddresses(response)) == 0 {
err = dns.RcodeSuccess
}
}
select {
case results <- queryResult{response, err}:
case <-returned:

View File

@@ -18,8 +18,6 @@ type Store struct {
logger logger.Logger
inet4Range netip.Prefix
inet6Range netip.Prefix
inet4Last netip.Addr
inet6Last netip.Addr
storage adapter.FakeIPStorage
addressAccess sync.Mutex
@@ -28,35 +26,12 @@ type Store struct {
}
func NewStore(ctx context.Context, logger logger.Logger, inet4Range netip.Prefix, inet6Range netip.Prefix) *Store {
store := &Store{
return &Store{
ctx: ctx,
logger: logger,
inet4Range: inet4Range,
inet6Range: inet6Range,
}
if inet4Range.IsValid() {
store.inet4Last = broadcastAddress(inet4Range)
}
if inet6Range.IsValid() {
store.inet6Last = broadcastAddress(inet6Range)
}
return store
}
func broadcastAddress(prefix netip.Prefix) netip.Addr {
addr := prefix.Addr()
raw := addr.As16()
bits := prefix.Bits()
if addr.Is4() {
bits += 96
}
for i := bits; i < 128; i++ {
raw[i/8] |= 1 << (7 - i%8)
}
if addr.Is4() {
return netip.AddrFrom4([4]byte(raw[12:]))
}
return netip.AddrFrom16(raw)
}
func (s *Store) Start() error {
@@ -74,10 +49,10 @@ func (s *Store) Start() error {
s.inet6Current = metadata.Inet6Current
} else {
if s.inet4Range.IsValid() {
s.inet4Current = s.inet4Range.Addr().Next()
s.inet4Current = s.inet4Range.Addr().Next().Next()
}
if s.inet6Range.IsValid() {
s.inet6Current = s.inet6Range.Addr().Next()
s.inet6Current = s.inet6Range.Addr().Next().Next()
}
_ = storage.FakeIPReset()
}
@@ -123,7 +98,7 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {
return netip.Addr{}, E.New("missing IPv4 fakeip address range")
}
nextAddress := s.inet4Current.Next()
if nextAddress == s.inet4Last || !s.inet4Range.Contains(nextAddress) {
if !s.inet4Range.Contains(nextAddress) {
nextAddress = s.inet4Range.Addr().Next().Next()
}
s.inet4Current = nextAddress
@@ -133,7 +108,7 @@ func (s *Store) Create(domain string, isIPv6 bool) (netip.Addr, error) {
return netip.Addr{}, E.New("missing IPv6 fakeip address range")
}
nextAddress := s.inet6Current.Next()
if nextAddress == s.inet6Last || !s.inet6Range.Contains(nextAddress) {
if !s.inet6Range.Contains(nextAddress) {
nextAddress = s.inet6Range.Addr().Next().Next()
}
s.inet6Current = nextAddress

View File

@@ -81,7 +81,10 @@ func (t *Transport) Reset() {
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
if t.resolved != nil {
return t.resolved.Exchange(ctx, message)
resolverObject := t.resolved.Object()
if resolverObject != nil {
return t.resolved.Exchange(resolverObject, ctx, message)
}
}
question := message.Question[0]
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {

View File

@@ -9,5 +9,6 @@ import (
type ResolvedResolver interface {
Start() error
Close() error
Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
Object() any
Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
}

View File

@@ -4,26 +4,19 @@ import (
"bufio"
"context"
"errors"
"net/netip"
"os"
"strings"
"sync"
"sync/atomic"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
dnsTransport "github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/service/resolved"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
@@ -56,23 +49,13 @@ type DBusResolvedResolver struct {
interfaceMonitor tun.DefaultInterfaceMonitor
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
systemBus *dbus.Conn
savedServerSet atomic.Pointer[resolvedServerSet]
resoledObject atomic.Pointer[ResolvedObject]
closeOnce sync.Once
}
type resolvedServerSet struct {
servers []resolvedServer
}
type resolvedServer struct {
primaryTransport adapter.DNSTransport
fallbackTransport adapter.DNSTransport
}
type resolvedServerSpecification struct {
address netip.Addr
port uint16
serverName string
type ResolvedObject struct {
dbus.BusObject
InterfaceIndex int32
}
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
@@ -99,31 +82,17 @@ func (t *DBusResolvedResolver) Start() error {
"org.freedesktop.DBus",
"NameOwnerChanged",
dbus.WithMatchSender("org.freedesktop.DBus"),
dbus.WithMatchArg(0, "org.freedesktop.resolve1"),
).Err
if err != nil {
return E.Cause(err, "configure resolved restart listener")
}
err = t.systemBus.BusObject().AddMatchSignal(
"org.freedesktop.DBus.Properties",
"PropertiesChanged",
dbus.WithMatchSender("org.freedesktop.resolve1"),
dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"),
).Err
if err != nil {
return E.Cause(err, "configure resolved properties listener")
return E.Cause(err, "configure resolved restart listener")
}
go t.loopUpdateStatus()
return nil
}
func (t *DBusResolvedResolver) Close() error {
var closeErr error
t.closeOnce.Do(func() {
serverSet := t.savedServerSet.Swap(nil)
if serverSet != nil {
closeErr = serverSet.Close()
}
if t.interfaceCallback != nil {
t.interfaceMonitor.UnregisterCallback(t.interfaceCallback)
}
@@ -131,97 +100,99 @@ func (t *DBusResolvedResolver) Close() error {
_ = t.systemBus.Close()
}
})
return closeErr
return nil
}
func (t *DBusResolvedResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
serverSet := t.savedServerSet.Load()
if serverSet == nil {
var err error
serverSet, err = t.checkResolved(context.Background())
if err != nil {
return nil, err
}
previousServerSet := t.savedServerSet.Swap(serverSet)
if previousServerSet != nil {
_ = previousServerSet.Close()
func (t *DBusResolvedResolver) Object() any {
return common.PtrOrNil(t.resoledObject.Load())
}
func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
question := message.Question[0]
resolvedObject := object.(*ResolvedObject)
call := resolvedObject.CallWithContext(
ctx,
"org.freedesktop.resolve1.Manager.ResolveRecord",
0,
resolvedObject.InterfaceIndex,
question.Name,
question.Qclass,
question.Qtype,
uint64(0),
)
if call.Err != nil {
var dbusError dbus.Error
if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" {
t.updateStatus()
}
return nil, E.Cause(call.Err, " resolve record via resolved")
}
response, err := t.exchangeServerSet(ctx, message, serverSet)
if err == nil {
return response, nil
}
t.updateStatus()
refreshedServerSet := t.savedServerSet.Load()
if refreshedServerSet == nil || refreshedServerSet == serverSet {
var (
records []resolved.ResourceRecord
outflags uint64
)
err := call.Store(&records, &outflags)
if err != nil {
return nil, err
}
return t.exchangeServerSet(ctx, message, refreshedServerSet)
response := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: mDNS.RcodeSuccess,
},
Question: []mDNS.Question{question},
}
for _, record := range records {
var rr mDNS.RR
rr, _, err = mDNS.UnpackRR(record.Data, 0)
if err != nil {
return nil, E.Cause(err, "unpack resource record")
}
response.Answer = append(response.Answer, rr)
}
return response, nil
}
func (t *DBusResolvedResolver) loopUpdateStatus() {
signalChan := make(chan *dbus.Signal, 1)
t.systemBus.Signal(signalChan)
for signal := range signalChan {
switch signal.Name {
case "org.freedesktop.DBus.NameOwnerChanged":
if len(signal.Body) != 3 {
continue
}
newOwner, loaded := signal.Body[2].(string)
if !loaded || newOwner == "" {
continue
}
t.updateStatus()
case "org.freedesktop.DBus.Properties.PropertiesChanged":
if !shouldUpdateResolvedServerSet(signal) {
var restarted bool
if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" {
if len(signal.Body) != 3 || signal.Body[2].(string) == "" {
continue
} else {
restarted = true
}
}
if restarted {
t.updateStatus()
}
}
}
func (t *DBusResolvedResolver) updateStatus() {
serverSet, err := t.checkResolved(context.Background())
oldServerSet := t.savedServerSet.Swap(serverSet)
if oldServerSet != nil {
_ = oldServerSet.Close()
}
dbusObject, err := t.checkResolved(context.Background())
oldValue := t.resoledObject.Swap(dbusObject)
if err != nil {
var dbusErr dbus.Error
if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwner" {
if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" {
t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable"))
}
if oldServerSet != nil {
if oldValue != nil {
t.logger.Debug("systemd-resolved service is gone")
}
return
} else if oldServerSet == nil {
} else if oldValue == nil {
t.logger.Debug("using systemd-resolved service as resolver")
}
}
func (t *DBusResolvedResolver) exchangeServerSet(ctx context.Context, message *mDNS.Msg, serverSet *resolvedServerSet) (*mDNS.Msg, error) {
if serverSet == nil || len(serverSet.servers) == 0 {
return nil, E.New("link has no DNS servers configured")
}
var lastError error
for _, server := range serverSet.servers {
response, err := server.primaryTransport.Exchange(ctx, message)
if err != nil && server.fallbackTransport != nil {
response, err = server.fallbackTransport.Exchange(ctx, message)
}
if err != nil {
lastError = err
continue
}
return response, nil
}
return nil, lastError
}
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServerSet, error) {
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) {
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
if err != nil {
@@ -249,19 +220,16 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServ
if linkObject == nil {
return nil, E.New("missing link object for default interface")
}
dnsOverTLSMode, err := loadResolvedLinkDNSOverTLS(linkObject)
dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS")
if err != nil {
return nil, err
}
linkDNSEx, err := loadResolvedLinkDNSEx(linkObject)
var linkDNS []resolved.LinkDNS
err = dnsProp.Store(&linkDNS)
if err != nil {
return nil, err
}
linkDNS, err := loadResolvedLinkDNS(linkObject)
if err != nil {
return nil, err
}
if len(linkDNSEx) == 0 && len(linkDNS) == 0 {
if len(linkDNS) == 0 {
for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() {
if inbound.Type() == C.TypeTun {
return nil, E.New("No appropriate name servers or networks for name found")
@@ -269,233 +237,12 @@ func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*resolvedServ
}
return nil, E.New("link has no DNS servers configured")
}
serverDialer, err := dialer.NewDefault(t.ctx, option.DialerOptions{
BindInterface: defaultInterface.Name,
UDPFragmentDefault: true,
})
if err != nil {
return nil, err
}
var serverSpecifications []resolvedServerSpecification
if len(linkDNSEx) > 0 {
for _, entry := range linkDNSEx {
serverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, entry.Port, entry.Name)
if !loaded {
continue
}
serverSpecifications = append(serverSpecifications, serverSpecification)
}
} else {
for _, entry := range linkDNS {
serverSpecification, loaded := buildResolvedServerSpecification(defaultInterface.Name, entry.Address, 0, "")
if !loaded {
continue
}
serverSpecifications = append(serverSpecifications, serverSpecification)
}
}
if len(serverSpecifications) == 0 {
return nil, E.New("no valid DNS servers on link")
}
serverSet := &resolvedServerSet{
servers: make([]resolvedServer, 0, len(serverSpecifications)),
}
for _, serverSpecification := range serverSpecifications {
server, createErr := t.createResolvedServer(serverDialer, dnsOverTLSMode, serverSpecification)
if createErr != nil {
_ = serverSet.Close()
return nil, createErr
}
serverSet.servers = append(serverSet.servers, server)
}
return serverSet, nil
}
func (t *DBusResolvedResolver) createResolvedServer(serverDialer N.Dialer, dnsOverTLSMode string, serverSpecification resolvedServerSpecification) (resolvedServer, error) {
if dnsOverTLSMode == "yes" {
primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true)
if err != nil {
return resolvedServer{}, err
}
return resolvedServer{
primaryTransport: primaryTransport,
}, nil
}
if dnsOverTLSMode == "opportunistic" {
primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, true)
if err != nil {
return resolvedServer{}, err
}
fallbackTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false)
if err != nil {
_ = primaryTransport.Close()
return resolvedServer{}, err
}
return resolvedServer{
primaryTransport: primaryTransport,
fallbackTransport: fallbackTransport,
}, nil
}
primaryTransport, err := t.createResolvedTransport(serverDialer, serverSpecification, false)
if err != nil {
return resolvedServer{}, err
}
return resolvedServer{
primaryTransport: primaryTransport,
return &ResolvedObject{
BusObject: dbusObject,
InterfaceIndex: int32(defaultInterface.Index),
}, nil
}
func (t *DBusResolvedResolver) createResolvedTransport(serverDialer N.Dialer, serverSpecification resolvedServerSpecification, useTLS bool) (adapter.DNSTransport, error) {
serverAddress := M.SocksaddrFrom(serverSpecification.address, resolvedServerPort(serverSpecification.port, useTLS))
if useTLS {
tlsAddress := serverSpecification.address
if tlsAddress.Zone() != "" {
tlsAddress = tlsAddress.WithZone("")
}
serverName := serverSpecification.serverName
if serverName == "" {
serverName = tlsAddress.String()
}
tlsConfig, err := tls.NewClient(t.ctx, t.logger, tlsAddress.String(), option.OutboundTLSOptions{
Enabled: true,
ServerName: serverName,
})
if err != nil {
return nil, err
}
serverTransport := dnsTransport.NewTLSRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeTLS, "", nil), serverDialer, serverAddress, tlsConfig)
err = serverTransport.Start(adapter.StartStateStart)
if err != nil {
_ = serverTransport.Close()
return nil, err
}
return serverTransport, nil
}
serverTransport := dnsTransport.NewUDPRaw(t.logger, dns.NewTransportAdapter(C.DNSTypeUDP, "", nil), serverDialer, serverAddress)
err := serverTransport.Start(adapter.StartStateStart)
if err != nil {
_ = serverTransport.Close()
return nil, err
}
return serverTransport, nil
}
func (s *resolvedServerSet) Close() error {
var errors []error
for _, server := range s.servers {
errors = append(errors, server.primaryTransport.Close())
if server.fallbackTransport != nil {
errors = append(errors, server.fallbackTransport.Close())
}
}
return E.Errors(errors...)
}
func buildResolvedServerSpecification(interfaceName string, rawAddress []byte, port uint16, serverName string) (resolvedServerSpecification, bool) {
address, loaded := netip.AddrFromSlice(rawAddress)
if !loaded {
return resolvedServerSpecification{}, false
}
if address.Is6() && address.IsLinkLocalUnicast() && address.Zone() == "" {
address = address.WithZone(interfaceName)
}
return resolvedServerSpecification{
address: address,
port: port,
serverName: serverName,
}, true
}
func resolvedServerPort(port uint16, useTLS bool) uint16 {
if port > 0 {
return port
}
if useTLS {
return 853
}
return 53
}
func loadResolvedLinkDNS(linkObject dbus.BusObject) ([]resolved.LinkDNS, error) {
dnsProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS")
if err != nil {
if isResolvedUnknownPropertyError(err) {
return nil, nil
}
return nil, err
}
var linkDNS []resolved.LinkDNS
err = dnsProperty.Store(&linkDNS)
if err != nil {
return nil, err
}
return linkDNS, nil
}
func loadResolvedLinkDNSEx(linkObject dbus.BusObject) ([]resolved.LinkDNSEx, error) {
dnsProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNSEx")
if err != nil {
if isResolvedUnknownPropertyError(err) {
return nil, nil
}
return nil, err
}
var linkDNSEx []resolved.LinkDNSEx
err = dnsProperty.Store(&linkDNSEx)
if err != nil {
return nil, err
}
return linkDNSEx, nil
}
func loadResolvedLinkDNSOverTLS(linkObject dbus.BusObject) (string, error) {
dnsOverTLSProperty, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNSOverTLS")
if err != nil {
if isResolvedUnknownPropertyError(err) {
return "", nil
}
return "", err
}
var dnsOverTLSMode string
err = dnsOverTLSProperty.Store(&dnsOverTLSMode)
if err != nil {
return "", err
}
return dnsOverTLSMode, nil
}
func isResolvedUnknownPropertyError(err error) bool {
var dbusError dbus.Error
return errors.As(err, &dbusError) && dbusError.Name == "org.freedesktop.DBus.Error.UnknownProperty"
}
func shouldUpdateResolvedServerSet(signal *dbus.Signal) bool {
if len(signal.Body) != 3 {
return true
}
changedProperties, loaded := signal.Body[1].(map[string]dbus.Variant)
if !loaded {
return true
}
for propertyName := range changedProperties {
switch propertyName {
case "DNS", "DNSEx", "DNSOverTLS":
return true
}
}
invalidatedProperties, loaded := signal.Body[2].([]string)
if !loaded {
return true
}
for _, propertyName := range invalidatedProperties {
switch propertyName {
case "DNS", "DNSEx", "DNSOverTLS":
return true
}
}
return false
}
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
t.updateStatus()
}

View File

@@ -7,6 +7,7 @@ import (
"syscall"
"time"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
@@ -48,6 +49,13 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
results := make(chan queryResult)
startRacer := func(ctx context.Context, fqdn string) {
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
if err == nil {
if response.Rcode != mDNS.RcodeSuccess {
err = dns.RcodeError(response.Rcode)
} else if len(dns.MessageToAddresses(response)) == 0 {
err = E.New(fqdn, ": empty result")
}
}
select {
case results <- queryResult{response, err}:
case <-returned:

View File

@@ -5,7 +5,6 @@ import (
"net"
"net/netip"
"os"
"strconv"
"syscall"
"time"
"unsafe"
@@ -64,9 +63,6 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
continue
}
dnsServerAddr = netip.AddrFrom16(sockaddr.Addr)
if sockaddr.ZoneId != 0 {
dnsServerAddr = dnsServerAddr.WithZone(strconv.FormatInt(int64(sockaddr.ZoneId), 10))
}
default:
// Unexpected type.
continue

View File

@@ -2,338 +2,6 @@
icon: material/alert-decagram
---
#### 1.13.7
* Fixes and improvements
#### 1.13.6
* Fixes and improvements
#### 1.13.5
* Fixes and improvements
#### 1.13.4
* Fixes and improvements
#### 1.13.3
* Add OpenWrt and Alpine APK packages to release **1**
* Backport to macOS 10.13 High Sierra **2**
* OCM service: Add WebSocket support for Responses API **3**
* Fixes and improvements
**1**:
Alpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix:
- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk`
- Alpine: `sing-box_{version}_linux_{architecture}.apk`
**2**:
Legacy macOS binaries (with `-legacy-macos-10.13` suffix) now support
macOS 10.13 High Sierra, built using Go 1.25 with patches
from [SagerNet/go](https://github.com/SagerNet/go).
**3**:
See [OCM](/configuration/service/ocm).
#### 1.13.2
* Fixes and improvements
#### 1.13.1
* Fixes and improvements
#### 1.12.14
* Backport fixes
#### 1.13.0
Important changes since 1.12:
* Add NaiveProxy outbound **1**
* Add pre-match support for `auto_redirect` **2**
* Improve `auto_redirect` **3**
* Add Chrome Root Store certificate option **4**
* Add new options for ACME DNS-01 challenge providers **5**
* Add Wi-Fi state support for Linux and Windows **6**
* Add curve preferences, pinned public key SHA256, mTLS and ECH `query_server_name` for TLS options **7**
* Add kTLS support **8**
* Add ICMP echo (ping) proxy support **9**
* Add `interface_address`, `network_interface_address` and `default_interface_address` rule items **10**
* Add `preferred_by` route rule item **11**
* Improve `local` DNS server **12**
* Add `disable_tcp_keep_alive`, `tcp_keep_alive` and `tcp_keep_alive_interval` options for listen and dial fields **13**
* Add `bind_address_no_port` option for dial fields **14**
* Add system interface, relay server and advertise tags options for Tailscale endpoint **15**
* Add Claude Code Multiplexer service **16**
* Add OpenAI Codex Multiplexer service **17**
* Apple/Android: Refactor GUI
* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs)
* Android: Add support for resisting VPN detection via Xposed
* Drop support for go1.23 **18**
* Drop support for Android 5.0 **19**
* Update uTLS to v1.8.2 **20**
* Update quic-go to v0.59.0
* Update gVisor to v20250811
* Update Tailscale to v1.92.4
**1**:
NaiveProxy outbound now supports QUIC, ECH, UDP over TCP, and configurable QUIC congestion control.
Only available on Apple platforms, Android, Windows and some Linux architectures.
Each Windows release includes `libcronet.dll`
ensure this file is in the same directory as `sing-box.exe` or in a directory listed in `PATH`.
See [NaiveProxy outbound](/configuration/outbound/naive/).
**2**:
`auto_redirect` now allows you to bypass sing-box for connections based on routing rules.
A new rule action `bypass` is introduced to support this feature. When matched during pre-match, the connection will bypass sing-box and connect directly.
This feature requires Linux with `auto_redirect` enabled.
See [Pre-match](/configuration/shared/pre-match/) and [Rule Action](/configuration/route/rule_action/#bypass).
**3**:
`auto_redirect` now rejects MPTCP connections by default to fix compatibility issues.
You can change it to bypass sing-box via the new `exclude_mptcp` option.
Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default),
ensuring traffic is routed to the sing-box table when no route is found in system tables.
The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768).
See [TUN](/configuration/inbound/tun/#exclude_mptcp).
**4**:
Adds `chrome` as a new certificate store option alongside `mozilla`.
Both stores filter out China-based CA certificates.
See [Certificate](/configuration/certificate/#store).
**5**:
See [DNS-01 Challenge](/configuration/shared/dns01_challenge/).
**6**:
sing-box can now monitor Wi-Fi state on Linux and Windows to enable routing rules based on `wifi_ssid` and `wifi_bssid`.
See [Wi-Fi State](/configuration/shared/wifi-state/).
**7**:
See [TLS](/configuration/shared/tls/).
**8**:
Adds `kernel_tx` and `kernel_rx` options for TLS inbound.
Enables kernel-level TLS offloading via `splice(2)` on Linux 5.1+ with TLS 1.3.
See [TLS](/configuration/shared/tls/).
**9**:
sing-box can now proxy ICMP echo (ping) requests.
A new `icmp` network type is available for route rules.
Supported from TUN, WireGuard and Tailscale inbounds to Direct, WireGuard and Tailscale outbounds.
The `reject` action can also reply to ICMP echo requests.
**10**:
New rule items for matching based on interface IP addresses, available in route rules, DNS rules and rule-sets.
**11**:
Matches outbounds' preferred routes.
For Tailscale: MagicDNS domains and peers' allowed IPs. For WireGuard: peers' allowed IPs.
**12**:
The `local` DNS server now uses platform-native resolution:
`getaddrinfo`/libresolv on Apple platforms, systemd-resolved DBus on Linux.
A new `prefer_go` option is available to opt out.
See [Local DNS](/configuration/dns/server/local/).
**13**:
The default TCP keep-alive initial period has been updated from 10 minutes to 5 minutes.
See [Dial Fields](/configuration/shared/dial/#tcp_keep_alive).
**14**:
Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address.
This allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios.
See [Dial Fields](/configuration/shared/dial/#bind_address_no_port).
**15**:
Tailscale endpoint can now create a system TUN interface to handle traffic directly.
New `relay_server_port` and `relay_server_static_endpoints` options for incoming relay connections.
New `advertise_tags` option for ACL tag advertisement.
See [Tailscale endpoint](/configuration/endpoint/tailscale/).
**16**:
CCM (Claude Code Multiplexer) service allows you to access your local Claude Code subscription remotely through custom tokens, eliminating the need for OAuth authentication on remote clients.
See [CCM](/configuration/service/ccm).
**17**:
See [OCM](/configuration/service/ocm).
**18**:
Due to maintenance difficulties, sing-box 1.13.0 requires at least Go 1.24 to compile.
**19**:
Due to maintenance difficulties, sing-box 1.13.0 will be the last version to support Android 5.0,
and only through a separate legacy build (with `-legacy-android-5` suffix).
For standalone binaries, the minimum Android version has been raised to Android 6.0,
since Termux requires Android 7.0 or later.
**20**:
This update fixes missing padding extension for Chrome 120+ fingerprints.
Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities.
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
use NaiveProxy instead for TLS fingerprint resistance.
#### 1.12.23
* Fixes and improvements
#### 1.13.0-rc.5
* Add `mipsle`, `mips64le`, `riscv64` and `loong64` support for NaiveProxy outbound
#### 1.12.22
* Fixes and improvements
#### 1.13.0-rc.3
* Fixes and improvements
#### 1.12.21
* Fixes and improvements
#### 1.13.0-rc.2
* Fixes and improvements
#### 1.12.20
* Fixes and improvements
#### 1.13.0-rc.1
* Fixes and improvements
#### 1.12.19
* Fixes and improvements
#### 1.13.0-beta.8
* Add fallback routing rule for `auto_redirect` **1**
* Fixes and improvements
**1**:
Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default),
ensuring traffic is routed to the sing-box table when no route is found in system tables.
The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768).
#### 1.12.18
* Add fallback routing rule for `auto_redirect` **1**
* Fixes and improvements
**1**:
Adds a fallback iproute2 rule checked after system default rules (32766: main, 32767: default),
ensuring traffic is routed to the sing-box table when no route is found in system tables.
The rule index can be customized via `auto_redirect_iproute2_fallback_rule_index` (default: 32768).
#### 1.13.0-beta.6
* Update uTLS to v1.8.2 **1**
* Fixes and improvements
**1**:
This update fixes missing padding extension for Chrome 120+ fingerprints.
Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities.
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
use NaiveProxy instead for TLS fingerprint resistance.
#### 1.12.17
* Update uTLS to v1.8.2 **1**
* Fixes and improvements
**1**:
This update fixes missing padding extension for Chrome 120+ fingerprints.
Also, documentation has been updated with a warning about uTLS fingerprinting vulnerabilities.
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
use NaiveProxy instead for TLS fingerprint resistance.
#### 1.13.0-beta.5
* Fixes and improvements
#### 1.12.16
* Fixes and improvements
#### 1.13.0-beta.4
* Apple/Android: Add support for sharing configurations via [QRS](https://github.com/qifi-dev/qrs)
* Android: Add support for resisting VPN detection via Xposed
* Update quic-go to v0.59.0
* Fixes and improvements
#### 1.13.0-beta.2
* Add `bind_address_no_port` option for dial fields **1**
* Fixes and improvements
**1**:
Adds the Linux socket option `IP_BIND_ADDRESS_NO_PORT` support when explicitly binding to a source address.
This allows reusing the same source port for multiple connections, improving scalability for high-concurrency proxy scenarios.
See [Dial Fields](/configuration/shared/dial/#bind_address_no_port).
#### 1.13.0-beta.1
* Add system interface support for Tailscale endpoint **1**

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.12.0 废弃"
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
### 结构

View File

@@ -209,7 +209,7 @@ icon: material/alert-decagram
(`source_port` || `source_port_range`) &&
`other fields`
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
#### inbound
@@ -546,4 +546,4 @@ Match any IP with query response.
#### rules
Included rules.
Included rules.

View File

@@ -208,7 +208,7 @@ icon: material/alert-decagram
(`source_port` || `source_port_range`) &&
`other fields`
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义
另外,引用规则集可视为被合并,而不是作为一个单独的规则子项
#### inbound
@@ -256,7 +256,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! failure "已在 sing-box 1.12.0 中被移除"
GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geosite-到规则集)。
GeoSite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
匹配 Geosite。
@@ -264,7 +264,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! failure "已在 sing-box 1.12.0 中被移除"
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
匹配源 GeoIP。
@@ -453,7 +453,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
!!! failure "已在 sing-box 1.12.0 废弃"
`outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-outbound-dns-规则项到域解析选项)。
`outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver)。
匹配出站。
@@ -505,7 +505,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
!!! failure "已在 sing-box 1.12.0 中被移除"
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移-geoip-到规则集)。
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
与查询响应匹配 GeoIP。
@@ -550,4 +550,4 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
==必填==
包括的规则。
包括的规则。

View File

@@ -64,7 +64,7 @@ DNS 服务器的路径。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段

View File

@@ -64,7 +64,7 @@ DNS 服务器的路径。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "Deprecated in sing-box 1.12.0"
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
!!! quote "sing-box 1.9.0 中的更改"

View File

@@ -51,7 +51,7 @@ DNS 服务器的端口。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段

View File

@@ -51,7 +51,7 @@ DNS 服务器的端口。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#出站)。
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段

View File

@@ -4,12 +4,11 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [relay_server_port](#relay_server_port)
:material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints)
:material-plus: [system_interface](#system_interface)
:material-plus: [system_interface_name](#system_interface_name)
:material-plus: [system_interface_mtu](#system_interface_mtu)
:material-plus: [advertise_tags](#advertise_tags)
:material-plus: [relay_server_port](#relay_server_port)
:material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints)
:material-plus: [system_interface](#system_interface)
:material-plus: [system_interface_name](#system_interface_name)
:material-plus: [system_interface_mtu](#system_interface_mtu)
!!! question "Since sing-box 1.12.0"
@@ -29,7 +28,6 @@ icon: material/new-box
"exit_node_allow_lan_access": false,
"advertise_routes": [],
"advertise_exit_node": false,
"advertise_tags": [],
"relay_server_port": 0,
"relay_server_static_endpoints": [],
"system_interface": false,
@@ -104,14 +102,6 @@ Example: `["192.168.1.1/24"]`
Indicates whether the node should advertise itself as an exit node.
#### advertise_tags
!!! question "Since sing-box 1.13.0"
Tags to advertise for this node, for ACL enforcement purposes.
Example: `["tag:server"]`
#### relay_server_port
!!! question "Since sing-box 1.13.0"

View File

@@ -4,12 +4,11 @@ icon: material/new-box
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [relay_server_port](#relay_server_port)
:material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints)
:material-plus: [system_interface](#system_interface)
:material-plus: [system_interface_name](#system_interface_name)
:material-plus: [system_interface_mtu](#system_interface_mtu)
:material-plus: [advertise_tags](#advertise_tags)
:material-plus: [relay_server_port](#relay_server_port)
:material-plus: [relay_server_static_endpoints](#relay_server_static_endpoints)
:material-plus: [system_interface](#system_interface)
:material-plus: [system_interface_name](#system_interface_name)
:material-plus: [system_interface_mtu](#system_interface_mtu)
!!! question "自 sing-box 1.12.0 起"
@@ -29,7 +28,6 @@ icon: material/new-box
"exit_node_allow_lan_access": false,
"advertise_routes": [],
"advertise_exit_node": false,
"advertise_tags": [],
"relay_server_port": 0,
"relay_server_static_endpoints": [],
"system_interface": false,
@@ -103,14 +101,6 @@ icon: material/new-box
指示节点是否应将自己通告为出口节点。
#### advertise_tags
!!! question "自 sing-box 1.13.0 起"
为此节点通告的标签,用于 ACL 执行。
示例:`["tag:server"]`
#### relay_server_port
!!! question "自 sing-box 1.13.0 起"

View File

@@ -42,7 +42,7 @@
将拒绝的 DNS 响应缓存存储在缓存文件中。
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#地址筛选字段) 的检查结果将被缓存至过期。
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。
#### rdrc_timeout

View File

@@ -1,6 +1,6 @@
!!! quote ""
默认安装不包含 V2Ray API参阅 [安装](/zh/installation/build-from-source/#构建标记)。
默认安装不包含 V2Ray API参阅 [安装](/zh/installation/build-from-source/#_5)。
### 结构

View File

@@ -58,4 +58,4 @@ AnyTLS 填充方案行数组。
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View File

@@ -26,7 +26,7 @@
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### users

View File

@@ -104,4 +104,4 @@ base64 编码的认证密码。
==必填==
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View File

@@ -38,7 +38,7 @@ icon: material/alert-decagram
!!! warning "与官方 Hysteria2 的区别"
官方程序支持一种名为 **userpass** 的验证方式,
本质上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
本质上是将用户名与密码的组合 `<username>:<password>` 作为实际上的密码,而 sing-box 不提供此别名。
要将 sing-box 与官方程序一起使用, 您需要填写该组合作为实际密码。
### 监听字段
@@ -85,7 +85,7 @@ Hysteria 用户
==必填==
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### masquerade

View File

@@ -60,4 +60,4 @@ QUIC 拥塞控制算法。
#### tls
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View File

@@ -93,4 +93,4 @@
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。

View File

@@ -43,7 +43,7 @@ Trojan 用户。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### fallback
@@ -61,7 +61,7 @@ TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#入站)。
#### multiplex
参阅 [多路复用](/zh/configuration/shared/multiplex#入站)。
参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。
#### transport

View File

@@ -75,4 +75,4 @@ QUIC 拥塞控制算法
==必填==
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#入站)。
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View File

@@ -2,21 +2,11 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [include_mac_address](#include_mac_address)
:material-plus: [exclude_mac_address](#exclude_mac_address)
!!! quote "Changes in sing-box 1.13.3"
:material-alert: [strict_route](#strict_route)
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [auto_redirect_reset_mark](#auto_redirect_reset_mark)
:material-plus: [auto_redirect_nfqueue](#auto_redirect_nfqueue)
:material-plus: [exclude_mptcp](#exclude_mptcp)
:material-plus: [auto_redirect_iproute2_fallback_rule_index](#auto_redirect_iproute2_fallback_rule_index)
:material-plus: [exclude_mptcp](#exclude_mptcp)
!!! quote "Changes in sing-box 1.12.0"
@@ -81,7 +71,6 @@ icon: material/new-box
"auto_redirect_output_mark": "0x2024",
"auto_redirect_reset_mark": "0x2025",
"auto_redirect_nfqueue": 100,
"auto_redirect_iproute2_fallback_rule_index": 32768,
"exclude_mptcp": false,
"loopback_address": [
"10.7.0.1"
@@ -314,17 +303,6 @@ NFQueue number used by `auto_redirect` pre-matching.
`100` is used by default.
#### auto_redirect_iproute2_fallback_rule_index
!!! question "Since sing-box 1.12.18"
Linux iproute2 fallback rule index generated by `auto_redirect`.
This rule is checked after system default rules (32766: main, 32767: default),
routing traffic to the sing-box table only when no route is found in system tables.
`32768` is used by default.
#### exclude_mptcp
!!! question "Since sing-box 1.13.0"
@@ -357,9 +335,6 @@ Enforce strict routing rules when `auto_route` is enabled:
* Let unsupported network unreachable
* For legacy reasons, when neither `strict_route` nor `auto_redirect` are enabled, all ICMP traffic will not go through TUN.
* When `auto_redirect` is enabled, `strict_route` also affects `SO_BINDTODEVICE` traffic:
* Enabled: `SO_BINDTODEVICE` traffic is redirected through sing-box.
* Disabled: `SO_BINDTODEVICE` traffic bypasses sing-box.
*In Windows*:

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