Compare commits

..

100 Commits

Author SHA1 Message Date
Sergei Maklagin
20bf40e822 Update dependencies 2026-03-11 17:32:12 +03:00
Sergei Maklagin
861aff60f0 Update dependencies 2026-03-11 06:45:24 +03:00
Sergei Maklagin
2df1cffb0f Resolve conflicts 2026-03-10 08:43:01 +03:00
Sergei Maklagin
f683fcb3cb Update dependencies 2026-03-10 08:37:04 +03:00
Sergei Maklagin
bc6ca6e2ea Update sing-box core 2026-03-10 04:50:32 +03:00
Sergei Maklagin
6cd8b832fe Update sing-box core 2026-03-10 04:25:01 +03:00
世界
c0d45aebfa Bump version 2026-03-07 15:55:42 +08:00
Sergei Maklagin
0503006f48 Add Kmutex 2026-03-03 00:51:20 +03:00
Sergei Maklagin
517f5152e7 Add migrate 2026-03-03 00:51:04 +03:00
Sergei Maklagin
b1b7aa81cd Update Amnezia H1-H4 format 2026-03-02 21:48:54 +03:00
Sergei Maklagin
195e941c35 Fix typo 2026-03-02 21:27:47 +03:00
Sergei Maklagin
35bc351564 Fix failover 2026-03-02 19:48:06 +03:00
Sergei Maklagin
290dbed7b8 Fix failover 2026-03-02 19:33:59 +03:00
Sergei Maklagin
d7a8207f44 Update AmneziaWG 2026-03-02 19:33:07 +03:00
Sergei Maklagin
57c5ca13eb Fix bond outbound 2026-03-02 19:31:23 +03:00
Sergei Maklagin
7fc33134fb Update AmneziaWG 2026-03-01 16:42:21 +03:00
Sergei Maklagin
881ab6d436 Fix examples 2026-02-27 00:47:22 +03:00
Sergei Maklagin
0443b93328 Update README.md 2026-02-27 00:19:37 +03:00
Sergei Maklagin
75557830a8 Merge branch 'extended' into extended-next 2026-02-26 22:58:59 +03:00
Sergei Maklagin
9d5273ba1e Resolve conflicts 2026-02-26 22:58:45 +03:00
Sergei Maklagin
5f2a65f01b Add examples 2026-02-26 22:57:44 +03:00
Sergei Maklagin
06a519db27 Resolve conflicts 2026-02-26 22:57:25 +03:00
Sergei Maklagin
65e73fe817 Add examples 2026-02-26 22:55:24 +03:00
Sergei Maklagin
c0aa3480c5 Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport 2026-02-26 22:44:31 +03:00
Sergei Maklagin
69f6c75dd7 Add vless encryption 2026-02-26 18:03:59 +03:00
Sergei Maklagin
b62000e924 Update README.md 2026-02-25 14:10:09 +03:00
Sergei Maklagin
a03af44c61 Add DONATE.md 2026-02-25 14:03:36 +03:00
Sergei Maklagin
aa103fdfc6 Fix examples 2026-02-22 18:10:15 +03:00
Sergei Maklagin
c82e613c52 Fix typo 2026-02-22 17:59:02 +03:00
Sergei Maklagin
3d16078651 Fix padding 2026-02-22 16:22:52 +03:00
Sergei Maklagin
18b1101fbe Update Dockerfile 2026-02-22 15:50:39 +03:00
Sergei Maklagin
4ebe870306 Update xhttp examples 2026-02-22 15:50:00 +03:00
Sergei Maklagin
50c5e9df0d Fix xhttp options 2026-02-22 15:46:12 +03:00
Sergei Maklagin
c8a993834e Fix Range 2026-02-22 15:45:53 +03:00
Sergei Maklagin
260bbbfb45 Fix examples 2026-02-22 14:51:16 +03:00
Sergei Maklagin
82337299b9 Update xhttp 2026-02-22 14:48:52 +03:00
Sergei Maklagin
c229c79dcc Update sing-box core 2026-02-22 14:46:42 +03:00
世界
f63091d14d Bump version 2026-02-15 21:05:34 +08:00
世界
1c4a01ee90 Fix matching multi predefined 2026-02-15 19:20:31 +08:00
世界
4d7f99310c Fix matching rule-set invert 2026-02-15 19:20:11 +08:00
世界
6fc511f56e wireguard: Fix missing fallback for gso 2026-02-15 19:20:03 +08:00
世界
d18d2b352a Bump version 2026-02-09 13:57:18 +08:00
世界
534128bba9 tuic: Fix udp context 2026-02-09 13:55:09 +08:00
世界
736a7368c6 Fix naive padding 2026-02-09 13:53:32 +08:00
世界
e7a9c90213 Fix DNS cache lock goroutine leak
The cache deduplication in Client.Exchange uses a channel-based lock
per DNS question. Waiting goroutines blocked on <-cond without context
awareness, causing them to accumulate indefinitely when the owning
goroutine's transport call stalls. Add select on ctx.Done() so waiters
respect context cancellation and timeouts.
2026-02-06 22:28:30 +08:00
世界
0f3774e501 Bump version 2026-02-05 17:13:38 +08:00
世界
2f8e656522 Update Go to 1.25.7 2026-02-05 17:12:42 +08:00
世界
3ba30e3f00 Fix route_address_set duplicated IP sets causing route creation failure
The FlatMap calls pre-populated routeAddressSet and routeExcludeAddressSet
before the for-loops which appended the same IP sets again, doubling every
entry. On Windows this caused CreateIpForwardEntry2 to return
ERROR_OBJECT_ALREADY_EXISTS.

Fixes #3725
2026-02-02 17:29:21 +08:00
世界
f2639a5829 Fix random iproute2 table index was incorrectly removed 2026-02-02 14:13:49 +08:00
世界
69bebbda82 Bump version 2026-02-01 10:19:35 +08:00
世界
00b2c042ee Disable rp filter atomically 2026-02-01 10:17:34 +08:00
世界
d9eb8f3ab6 Fix varbin serialization 2026-02-01 10:11:15 +08:00
世界
58025a01f8 Fix auto_redirect fallback rule 2026-01-29 12:07:15 +08:00
世界
99cad72ea8 Bump version 2026-01-28 16:56:08 +08:00
世界
6e96d620fe Minor fixes 2026-01-28 16:56:08 +08:00
Sergei Maklagin
596291567f Update AmneziaWG 2026-01-25 21:24:43 +03:00
Sergei Maklagin
a2a5f46cb6 Resolve conflicts 2026-01-18 21:53:22 +03:00
Sergei Maklagin
f6da8e52b4 Fix logger 2026-01-18 21:51:30 +03:00
世界
b27d707668 Bump version 2026-01-17 04:54:24 +08:00
Sergei Maklagin
287fe834db Update XHTTP 2025-12-11 02:46:57 +03:00
Sergei Maklagin
d7f0cea4ff Fix typo 2025-12-08 23:18:51 +03:00
Sergei Maklagin
d8b470d1ba Add new wireguard options 2025-12-08 22:32:33 +03:00
Sergei Maklagin
984fc295b3 Fix XHTTP TLS 2025-12-08 22:30:58 +03:00
Shtorm
6e4b7ed744 Merge pull request #6 from starifly/extended
fix(xhttp): use download request URL for down leg
2025-11-03 19:13:46 +03:00
starifly
855d400654 fix(xhttp): use download request URL for down leg 2025-11-03 23:18:03 +08:00
Sergei Maklagin
4c5e2c6645 Remove direct detour checking 2025-11-02 17:59:12 +03:00
Sergei Maklagin
725eccdea8 Fix Range 2025-11-02 17:55:18 +03:00
Sergei Maklagin
2ff042abd2 Resolve unnecessary logger 2025-11-02 17:41:18 +03:00
Sergei Maklagin
ffb282e47e Resolve conflicts 2025-11-02 17:39:38 +03:00
Sergei Maklagin
91f9134379 Update README.md 2025-09-15 01:25:46 +03:00
Sergei Maklagin
5a4de7b242 Integrate Amnezia 1.5 2025-09-15 01:25:19 +03:00
Sergei Maklagin
bc91312a73 Update go.sum 2025-09-14 23:45:55 +03:00
Sergei Maklagin
1d603e24fd Resolve conflicts 2025-09-14 23:44:29 +03:00
Sergei Maklagin
9dc526ea1f Resolve conflicts 2025-08-15 12:57:47 +03:00
Sergei Maklagin
93eb435e26 Resolve conflicts 2025-08-15 12:56:52 +03:00
Sergei Maklagin
e22416f0d9 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2025-07-14 13:27:54 +03:00
Sergei Maklagin
89497dbfd5 Update dependencies 2025-07-13 21:48:31 +03:00
Sergei Maklagin
8388abbb77 Resolve conflicts 2025-07-13 21:44:23 +03:00
Sergei Maklagin
180b7c4134 Fix tunnel client 2025-07-13 21:41:41 +03:00
Sergei Maklagin
deda2cca5e Fix xhttp transport 2025-07-13 21:40:34 +03:00
Shtorm
691ecd45a9 Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2025-07-06 21:16:54 +03:00
Sergei Maklagin
209b89a4a3 Add tunnel 2025-07-06 18:31:06 +03:00
Sergei Maklagin
765111a552 Merge tag 'v1.11.14' into HEAD 2025-06-19 20:18:51 +03:00
Sergei Maklagin
824eac453b Add new examples 2025-06-15 22:23:42 +03:00
Sergei Maklagin
85a1a8a53b Format examples 2025-06-15 22:23:25 +03:00
Sergei Maklagin
ae9e7aa5f4 Fix Range 2025-06-15 22:21:58 +03:00
Sergei Maklagin
52b71c6f00 Add sdns transport 2025-06-15 20:47:05 +03:00
Sergei Maklagin
5d04673783 Fix XHTTP Fqdn 2025-06-15 19:41:22 +03:00
Sergei Maklagin
307c429715 Fix WARP endpoint error 2025-06-15 19:40:42 +03:00
Sergei Maklagin
6801b58d96 Fix interrupt_exist_connections 2025-06-15 19:32:14 +03:00
Sergei Maklagin
6272596ebc Add unified delay 2025-06-15 19:24:09 +03:00
Sergei Maklagin
7c4c2d5ca8 Add mieru protocol 2025-06-15 18:04:27 +03:00
Sergei Maklagin
6768c77fa0 Merge tag 'v1.11.13' into HEAD 2025-06-08 22:43:21 +03:00
Sergei Maklagin
0dff811977 Add examples 2025-06-08 22:38:32 +03:00
Sergei Maklagin
e1a58d12fe Resolve conflicts 2025-06-08 19:54:17 +03:00
Sergei Maklagin
4e74a4108d refactor: WARP 2025-06-08 19:51:17 +03:00
Sergei Maklagin
0238c54261 Add xhttp transport 2025-06-08 19:35:59 +03:00
Sergei Maklagin
5c911c97d8 Added WARP endpoint 2025-06-01 22:06:34 +03:00
Sergei Maklagin
2cfc8092ad Fix endpoint manager locks 2025-05-29 22:48:15 +03:00
Sergei Maklagin
26bd698462 Integrate AmneziaWG 2025-05-24 23:54:15 +03:00
761 changed files with 9329 additions and 74267 deletions

View File

@@ -1,16 +1,12 @@
-s dir
--name sing-box-extended
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform (extended)."
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--no-deb-generate-changes
--provides sing-box
--conflicts sing-box
--replaces sing-box
--config-files /etc/config/sing-box
--config-files /etc/sing-box/config.json

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 @@
98d539ce67568fb911654e66a14cf4247ed833ec
cba7b9ac0399055aa49fbdc57c03c374f58e1597

View File

@@ -1,94 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
prepare_apk_root() {
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
APK_ROOT_DIR=$(mktemp -d)
mkdir -p "$APK_ROOT_DIR/etc"
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
EOF
cat > "$APK_ROOT_DIR/etc/group" <<EOF
root:x:$(id -g):root
EOF
}
ARCHITECTURE="$1"
VERSION="$2"
BINARY_PATH="$3"
OUTPUT_PATH="$4"
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
exit 1
fi
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
# Convert version to APK format:
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
# 1.13.0 -> 1.13.0-r0
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
APK_VERSION="${APK_VERSION}-r0"
ROOT_DIR=$(mktemp -d)
prepare_apk_root
trap 'rm -rf "$ROOT_DIR" "$APK_ROOT_DIR"' EXIT
# Binary
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
# Config files
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
install -Dm755 "$PROJECT/release/config/sing-box.initd" "$ROOT_DIR/etc/init.d/sing-box"
install -Dm644 "$PROJECT/release/config/sing-box.confd" "$ROOT_DIR/etc/conf.d/sing-box"
# Service files
install -Dm644 "$PROJECT/release/config/sing-box.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box.service"
install -Dm644 "$PROJECT/release/config/sing-box@.service" "$ROOT_DIR/usr/lib/systemd/system/sing-box@.service"
# Completions
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
# License
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
# APK metadata
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
mkdir -p "$PACKAGES_DIR"
# .conffiles
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
/etc/conf.d/sing-box
/etc/init.d/sing-box
/etc/sing-box/config.json
EOF
# .conffiles_static (sha256 checksums)
while IFS= read -r conffile; do
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
echo "$conffile $sha256"
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
# .list (all files, excluding lib/apk/packages/ metadata)
(cd "$ROOT_DIR" && find . -type f -o -type l) \
| sed 's|^\./|/|' \
| grep -v '^/lib/apk/packages/' \
| sort > "$PACKAGES_DIR/.list"
# Build APK
apk --root "$APK_ROOT_DIR" mkpkg \
--info "name:sing-box" \
--info "version:${APK_VERSION}" \
--info "description:The universal proxy platform." \
--info "arch:${ARCHITECTURE}" \
--info "license:GPL-3.0-or-later with name use or association addition" \
--info "origin:sing-box" \
--info "url:https://sing-box.sagernet.org/" \
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
--files "$ROOT_DIR" \
--output "$OUTPUT_PATH"

View File

@@ -1,91 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
prepare_apk_root() {
# apk mkpkg resolves owner/group names through --root/etc/{passwd,group}.
APK_ROOT_DIR=$(mktemp -d)
mkdir -p "$APK_ROOT_DIR/etc"
cat > "$APK_ROOT_DIR/etc/passwd" <<EOF
root:x:$(id -u):$(id -g):root:/root:/sbin/nologin
EOF
cat > "$APK_ROOT_DIR/etc/group" <<EOF
root:x:$(id -g):root
EOF
}
ARCHITECTURE="$1"
VERSION="$2"
BINARY_PATH="$3"
OUTPUT_PATH="$4"
if [ -z "$ARCHITECTURE" ] || [ -z "$VERSION" ] || [ -z "$BINARY_PATH" ] || [ -z "$OUTPUT_PATH" ]; then
echo "Usage: $0 <architecture> <version> <binary_path> <output_path>"
exit 1
fi
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
# Convert version to APK format:
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/' | sed -E 's/-[a-z]+-/./g')
APK_VERSION="${APK_VERSION}-r0"
ROOT_DIR=$(mktemp -d)
prepare_apk_root
trap 'rm -rf "$ROOT_DIR" "$APK_ROOT_DIR"' EXIT
# Binary
install -Dm755 "$BINARY_PATH" "$ROOT_DIR/usr/bin/sing-box"
# Config files
install -Dm644 "$PROJECT/release/config/config.json" "$ROOT_DIR/etc/sing-box/config.json"
install -Dm644 "$PROJECT/release/config/openwrt.conf" "$ROOT_DIR/etc/config/sing-box"
install -Dm755 "$PROJECT/release/config/openwrt.init" "$ROOT_DIR/etc/init.d/sing-box"
install -Dm644 "$PROJECT/release/config/openwrt.keep" "$ROOT_DIR/lib/upgrade/keep.d/sing-box"
# Completions
install -Dm644 "$PROJECT/release/completions/sing-box.bash" "$ROOT_DIR/usr/share/bash-completion/completions/sing-box.bash"
install -Dm644 "$PROJECT/release/completions/sing-box.fish" "$ROOT_DIR/usr/share/fish/vendor_completions.d/sing-box.fish"
install -Dm644 "$PROJECT/release/completions/sing-box.zsh" "$ROOT_DIR/usr/share/zsh/site-functions/_sing-box"
# License
install -Dm644 "$PROJECT/LICENSE" "$ROOT_DIR/usr/share/licenses/sing-box/LICENSE"
# APK metadata
PACKAGES_DIR="$ROOT_DIR/lib/apk/packages"
mkdir -p "$PACKAGES_DIR"
# .conffiles
cat > "$PACKAGES_DIR/.conffiles" <<'EOF'
/etc/config/sing-box
/etc/sing-box/config.json
EOF
# .conffiles_static (sha256 checksums)
while IFS= read -r conffile; do
sha256=$(sha256sum "$ROOT_DIR$conffile" | cut -d' ' -f1)
echo "$conffile $sha256"
done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
# .list (all files, excluding lib/apk/packages/ metadata)
(cd "$ROOT_DIR" && find . -type f -o -type l) \
| sed 's|^\./|/|' \
| grep -v '^/lib/apk/packages/' \
| sort > "$PACKAGES_DIR/.list"
# Build APK
apk --root "$APK_ROOT_DIR" mkpkg \
--info "name:sing-box-extended" \
--info "version:${APK_VERSION}" \
--info "description:The universal proxy platform (extended)." \
--info "arch:${ARCHITECTURE}" \
--info "license:GPL-3.0-or-later" \
--info "origin:sing-box-extended" \
--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 "provides:sing-box" \
--info "provider-priority:100" \
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
--files "$ROOT_DIR" \
--output "$OUTPUT_PATH"

View File

@@ -1,57 +0,0 @@
#!/usr/bin/env bash
set -e -o pipefail
VERSION="$1"
TARGET="$2"
BINARY_PATH="$3"
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
DIST="$PROJECT/dist"
case "$TARGET" in
linux_amd64*) ARCHITECTURES="x86_64" ;;
linux_arm64*) ARCHITECTURES="aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" ;;
linux_386*softfloat) ARCHITECTURES="i386_pentium-mmx" ;;
linux_386*) ARCHITECTURES="i386_pentium4" ;;
linux_arm_7*) ARCHITECTURES="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" ;;
linux_arm_6*) ARCHITECTURES="arm_arm1176jzf-s_vfp" ;;
linux_arm_5*) ARCHITECTURES="arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" ;;
linux_mips64_*) ARCHITECTURES="mips64_mips64r2 mips64_octeonplus" ;;
linux_mips64le*) ARCHITECTURES="mips64el_mips64r2" ;;
linux_mipsle*hardfloat) ARCHITECTURES="mipsel_24kc_24kf" ;;
linux_mipsle*) ARCHITECTURES="mipsel_24kc mipsel_74kc mipsel_mips32" ;;
linux_mips_*) ARCHITECTURES="mips_24kc mips_4kec mips_mips32" ;;
linux_riscv64*) ARCHITECTURES="riscv64_generic" ;;
linux_loong64*) ARCHITECTURES="loongarch64_generic" ;;
*) echo "Unknown target: $TARGET"; exit 1 ;;
esac
PKG_VERSION="${VERSION//-/\~}"
FPM_DIR=$(mktemp -d)
sed "s|release/|$PROJECT/release/|g;s|^LICENSE|$PROJECT/LICENSE|" "$PROJECT/.fpm_openwrt" > "$FPM_DIR/.fpm"
trap 'rm -rf "$FPM_DIR"' EXIT
for ARCH in $ARCHITECTURES; do
TMP_DEB=$(mktemp -p "$DIST" _openwrt_XXXXXX.deb)
rm -f "$TMP_DEB"
(cd "$FPM_DIR" && fpm -t deb \
-v "$PKG_VERSION" \
-p "$TMP_DEB" \
--architecture all \
"$BINARY_PATH=/usr/bin/sing-box")
bash "$PROJECT/.github/deb2ipk.sh" \
"$ARCH" \
"$TMP_DEB" \
"$DIST/sing-box-extended_${VERSION}_openwrt_${ARCH}.ipk"
rm -f "$TMP_DEB"
if command -v apk &>/dev/null; then
bash "$PROJECT/.github/build_openwrt_apk.sh" \
"$ARCH" "$VERSION" "$BINARY_PATH" \
"$DIST/sing-box-extended_${VERSION}_openwrt_${ARCH}.apk"
fi
echo "Built: sing-box-extended_${VERSION}_openwrt_${ARCH} (.ipk/.apk)"
done

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

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.25.10"
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.7"
VERSION="1.25.10"
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

@@ -47,7 +47,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -72,27 +72,27 @@ 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: arm, variant: musl, naive: true, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
- { os: linux, arch: mipsle, gomips: hardfloat, naive: true, variant: glibc }
- { os: linux, arch: mipsle, gomips: softfloat, naive: true, variant: musl, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
- { os: linux, arch: mips64le, gomips: hardfloat, naive: true, variant: glibc, debian: mips64el, rpm: mips64el }
- { os: linux, arch: riscv64, naive: true, variant: glibc }
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: riscv64, alpine: riscv64, openwrt: "riscv64_generic" }
- { os: linux, arch: riscv64, naive: true, variant: musl, debian: riscv64, rpm: 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: loong64, naive: true, variant: musl, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { 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" }
@@ -121,10 +121,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.10
go-version: ~1.25.7
- 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 +137,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
@@ -240,8 +243,7 @@ jobs:
if: matrix.variant == 'purego' && matrix.naive
run: |
cd ~/cronet-go
CGO_ENABLED=0 go build -v -o "$RUNNER_TEMP/build-naive" ./cmd/build-naive
GOPROXY=direct GOSUMDB=off "$RUNNER_TEMP/build-naive" extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist
CGO_ENABLED=0 go run -v ./cmd/build-naive extract-lib --target ${{ matrix.os }}/${{ matrix.arch }} -o $GITHUB_WORKSPACE/dist
- name: Build (glibc)
if: matrix.variant == 'glibc'
run: |
@@ -397,30 +399,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
@@ -456,36 +434,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"
@@ -493,7 +457,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
if [[ "${{ matrix.legacy_osx }}" != "true" ]]; then
if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then
TAGS=$(cat release/DEFAULT_BUILD_TAGS)
else
TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)
@@ -513,7 +477,6 @@ jobs:
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: |-
@@ -595,15 +558,8 @@ jobs:
if: matrix.naive
run: |
$CRONET_GO_VERSION = Get-Content .github/CRONET_GO_VERSION
git init "$env:RUNNER_TEMP/cronet-go"
git -C "$env:RUNNER_TEMP/cronet-go" remote add origin https://github.com/sagernet/cronet-go.git
git -C "$env:RUNNER_TEMP/cronet-go" fetch --depth=1 origin "$CRONET_GO_VERSION"
git -C "$env:RUNNER_TEMP/cronet-go" checkout FETCH_HEAD
$env:CGO_ENABLED = "0"
go -C "$env:RUNNER_TEMP/cronet-go" build -v -o "$env:RUNNER_TEMP/build-naive.exe" ./cmd/build-naive
$env:GOPROXY = "direct"
$env:GOSUMDB = "off"
& "$env:RUNNER_TEMP/build-naive.exe" extract-lib --target windows/${{ matrix.arch }} -o dist
go run -v "github.com/sagernet/cronet-go/cmd/build-naive@$CRONET_GO_VERSION" extract-lib --target windows/${{ matrix.arch }} -o dist
- name: Archive
if: matrix.naive
run: |
@@ -649,7 +605,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -681,10 +637,6 @@ jobs:
run: |-
cd clients/android
git checkout dev
- name: Checkout submodule recursive
run: |-
cd clients/android
git submodule update --init --recursive -v
- name: Gradle cache
uses: actions/cache@v4
with:
@@ -743,7 +695,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -842,7 +794,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Set tag
if: matrix.if
run: |-

View File

@@ -19,6 +19,7 @@ env:
jobs:
build_binary:
name: Build binary
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
runs-on: ubuntu-latest
strategy:
fail-fast: true
@@ -55,7 +56,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -259,13 +260,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 +286,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

@@ -18,60 +18,21 @@ on:
- testing
- unstable
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
cancel-in-progress: true
jobs:
build:
name: Lint ${{ matrix.goos }}/${{ matrix.goarch }}
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- goos: windows
goarch: amd64
- goos: windows
goarch: '386'
- goos: windows
goarch: arm64
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
- goos: linux
goarch: arm
- goos: linux
goarch: '386'
- goos: darwin
goarch: amd64
- goos: darwin
goarch: arm64
- goos: android
goarch: arm64
# - goos: freebsd
# goarch: amd64
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
with:
version: latest
args: --timeout=30m

View File

@@ -11,6 +11,11 @@ on:
description: "Version name"
required: true
type: string
forceBeta:
description: "Force beta"
required: false
type: boolean
default: false
release:
types:
- published
@@ -18,6 +23,7 @@ on:
jobs:
calculate_version:
name: Calculate version
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.outputs.outputs.version }}
@@ -29,7 +35,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -72,7 +78,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ~1.25.10
go-version: ~1.25.7
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -162,8 +168,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 }}"

9
.gitignore vendored
View File

@@ -21,12 +21,3 @@
CLAUDE.md
AGENTS.md
/.claude/
dist
logs
/*.so
/*.log
/*.db-shm
/*.db-wal
/*.db.backup.*
/test_download.bin
/wget-log

6
.gitmodules vendored
View File

@@ -0,0 +1,6 @@
[submodule "clients/apple"]
path = clients/apple
url = https://github.com/SagerNet/sing-box-for-apple.git
[submodule "clients/android"]
path = clients/android
url = https://github.com/SagerNet/sing-box-for-android.git

View File

@@ -1,6 +1,6 @@
version: "2"
run:
go: "1.24"
go: "1.25"
build-tags:
- with_gvisor
- with_quic
@@ -17,29 +17,30 @@ run:
linters:
default: none
enable:
- govet
- ineffassign
- paralleltest
- staticcheck
- unused
- modernize
settings:
modernize:
disable:
- omitzero # nested struct omitempty -> omitzero changes JSON output semantics
staticcheck:
checks:
- all
- -QF1008 # could remove embedded field "<interface>" from selector
- -ST1003 # should not use ALL_CAPS in Go names; use CamelCase instead
- -QF1001 # could apply De Morgan's law
- -S1000
- -S1008
- -S1017
- -ST1003
- -QF1001
- -QF1003
- -QF1008
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- transport/simple-obfs
- \.pb\.go$
- third_party$
- builtin$
- examples$
@@ -54,3 +55,10 @@ formatters:
- prefix(github.com/sagernet/)
- default
custom-order: true
exclusions:
generated: lax
paths:
- transport/simple-obfs
- third_party$
- builtin$
- examples$

View File

@@ -11,7 +11,6 @@ builds:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
- -checklinkname=0
tags:
- with_gvisor
- with_quic
@@ -21,17 +20,6 @@ builds:
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
@@ -43,13 +31,62 @@ builds:
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips
- linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- linux_mips64
- linux_mips64le
- windows_amd64_v1
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: mips
- id: manager
main: ./cmd/sing-box
flags:
- -v
- -trimpath
ldflags:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_manager
- with_admin_panel
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_6
- linux_arm_7
- linux_s390x
- linux_riscv64
- linux_mips
- linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- linux_mips64
- linux_mips64le
- windows_amd64_v1
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
- id: legacy
<<: *template
tags:
- with_gvisor
@@ -60,22 +97,11 @@ builds:
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_profiler
- badlinkname
- tfogo_checklinkname0
env:
- CGO_ENABLED=0
targets:
- linux_mips
- linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- linux_mips64
- linux_mips64le
- windows_amd64_v1
- windows_386
- id: android
<<: *template
env:
@@ -109,367 +135,11 @@ builds:
- android_arm64
- android_386
- android_amd64
- id: naive-purego-linux-amd64
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
hooks:
post:
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
env:
- CGO_ENABLED=0
targets:
- linux_amd64_v1
- id: naive-purego-linux-arm64
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
hooks:
post:
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
env:
- CGO_ENABLED=0
targets:
- linux_arm64
- id: naive-purego-windows-amd64
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
hooks:
post:
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
env:
- CGO_ENABLED=0
targets:
- windows_amd64_v1
- id: naive-purego-windows-arm64
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
hooks:
post:
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
env:
- CGO_ENABLED=0
targets:
- windows_arm64
- id: naive-glibc
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
env:
- CGO_ENABLED=1
- GOTOOLCHAIN=local
- CGO_LDFLAGS=-fuse-ld=lld -Wl,-z,notext -Wl,-z,execstack
- CC_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=x86_64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_amd64_staging
- CXX_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=x86_64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_amd64_staging
- CC_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=aarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_arm64_staging
- CXX_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=aarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_arm64_staging
- CC_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=i686-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_i386_staging
- CXX_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=i686-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_i386_staging
- CC_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=arm-linux-gnueabihf --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_armhf_staging
- CXX_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=arm-linux-gnueabihf --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_armhf_staging
- CC_linux_mips64le={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=mips64el-linux-gnuabi64 --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_mips64el_staging
- CXX_linux_mips64le={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=mips64el-linux-gnuabi64 --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_mips64el_staging
- CC_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=riscv64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/trixie/trixie_riscv64_staging
- CXX_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=riscv64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/trixie/trixie_riscv64_staging
- CC_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=loongarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/sid/sid_loong64_staging
- CXX_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=loongarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/sid/sid_loong64_staging
- 'CC={{ index .Env (print "CC_" .Os "_" .Arch) }}'
- 'CXX={{ index .Env (print "CXX_" .Os "_" .Arch) }}'
targets:
- linux_amd64_v1
- linux_arm64
- linux_386
- linux_arm_7
- linux_riscv64
- linux_loong64
- id: naive-musl
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_musl
env:
- CGO_ENABLED=1
- GOTOOLCHAIN=local
- CGO_LDFLAGS=-fuse-ld=lld -Wl,-z,notext -Wl,-z,execstack
- CC_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=x86_64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/x86_64
- CXX_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=x86_64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/x86_64
- CC_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=aarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/aarch64
- CXX_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=aarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/aarch64
- CC_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=i486-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/i386_pentium4
- CXX_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=i486-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/i386_pentium4
- CC_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=arm-openwrt-linux-musleabi --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/arm_cortex-a15_neon-vfpv4
- CXX_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=arm-openwrt-linux-musleabi --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/arm_cortex-a15_neon-vfpv4
- CC_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=riscv64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/riscv64
- CXX_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=riscv64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/riscv64
- CC_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=loongarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/24.10.5/loongarch64
- CXX_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=loongarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/24.10.5/loongarch64
- 'CC={{ index .Env (print "CC_" .Os "_" .Arch) }}'
- 'CXX={{ index .Env (print "CXX_" .Os "_" .Arch) }}'
targets:
- linux_amd64_v1
- linux_arm64
- linux_386
- linux_arm_7
- linux_riscv64
- linux_loong64
- id: compressed
<<: *template
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_6
- linux_arm_7
- linux_riscv64
- id: compressed-mips
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_profiler
- badlinkname
- tfogo_checklinkname0
targets:
- linux_mips
- linux_mips_softfloat
- linux_mipsle
- linux_mipsle_softfloat
- id: compressed-android
<<: *template
env:
- CGO_ENABLED=1
- GOTOOLCHAIN=local
overrides:
- goos: android
goarch: arm
goarm: 7
env:
- CC=armv7a-linux-androideabi21-clang
- CXX=armv7a-linux-androideabi21-clang++
- goos: android
goarch: arm64
env:
- CC=aarch64-linux-android21-clang
- CXX=aarch64-linux-android21-clang++
- goos: android
goarch: 386
env:
- CC=i686-linux-android21-clang
- CXX=i686-linux-android21-clang++
- goos: android
goarch: amd64
goamd64: v1
env:
- CC=x86_64-linux-android21-clang
- CXX=x86_64-linux-android21-clang++
targets:
- android_arm_7
- android_arm64
- android_386
- android_amd64
- id: openwrt
<<: *template
hooks:
post:
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
targets:
- linux_amd64_v1
- linux_arm64
- linux_386
- linux_arm_7
- linux_arm_6
- linux_riscv64
- linux_loong64
- id: openwrt-mips
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_ccm
- with_ocm
- with_openvpn
- with_trusttunnel
- with_sudoku
- with_snell
- with_profiler
- badlinkname
- tfogo_checklinkname0
hooks:
post:
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
targets:
- linux_arm_5
- linux_mips_softfloat
- linux_mips64_softfloat
- linux_mipsle_softfloat
- linux_mipsle_hardfloat
- linux_mips64le
upx:
- enabled: true
ids:
- compressed
- compressed-mips
- compressed-android
compress: best
lzma: true
archives:
- &template
id: archive
builds:
- main
- mips
- android
formats:
- tar.gz
@@ -481,59 +151,24 @@ archives:
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-naive-purego-linux-amd64
<<: *template
- id: archive_with_manager
builds:
- naive-purego-linux-amd64
- manager
formats:
- tar.gz
format_overrides:
- goos: windows
formats:
- zip
wrap_in_directory: true
files:
- LICENSE
- src: dist/naive-purego-linux-amd64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-purego-linux-arm64
name_template: '{{ .ProjectName }}-{{ .Version }}-with-manager-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-legacy
<<: *template
builds:
- naive-purego-linux-arm64
files:
- LICENSE
- src: dist/naive-purego-linux-arm64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-purego-windows-amd64
<<: *template
builds:
- naive-purego-windows-amd64
files:
- LICENSE
- src: dist/naive-purego-windows-amd64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-purego-windows-arm64
<<: *template
builds:
- naive-purego-windows-arm64
files:
- LICENSE
- src: dist/naive-purego-windows-arm64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-glibc
<<: *template
builds:
- naive-glibc
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-glibc'
- id: archive-naive-musl
<<: *template
builds:
- naive-musl
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-musl'
- id: archive-compressed
<<: *template
builds:
- compressed
- compressed-mips
- compressed-android
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-compressed'
- legacy
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
@@ -552,12 +187,5 @@ release:
mode: replace
ids:
- archive
- archive-naive-glibc
- archive-naive-musl
- archive-naive-purego-linux-amd64
- archive-naive-purego-linux-arm64
- archive-naive-purego-windows-amd64
- archive-naive-purego-windows-arm64
- archive-compressed
- archive-openwrt
- package
skip_upload: true

View File

@@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
LABEL maintainer="shtorm-7"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box

View File

@@ -14,37 +14,12 @@ PREFIX ?= $(shell go env GOPATH)
SING_FFI ?= sing-ffi
LIBBOX_FFI_CONFIG ?= ./experimental/libbox/ffi.json
ADMIN_PANEL_DIR = service/admin_panel
ADMIN_PANEL_WEB = $(ADMIN_PANEL_DIR)/web
ADMIN_PANEL_DIST = $(ADMIN_PANEL_DIR)/dist
ADMIN_PANEL_TAGS = $(TAGS),with_admin_panel
DOCKER_IMAGE ?= shtorm7/sing-box-extended
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
CRONET_GO_PATH ?= $(shell pwd)/cronet-go
.PHONY: test release docs build
build:
export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN)
build_admin_panel:
cd $(ADMIN_PANEL_WEB) && \
npm install --no-fund --no-audit && \
npm run build
go run ./cmd/internal/admin_panel_pack \
-dir $(ADMIN_PANEL_DIST)
build_naive:
cd $(CRONET_GO_PATH) && \
for arch in amd64 arm64 386 arm mipsle mips64le riscv64 loong64; do \
go run ./cmd/build-naive --target=linux/$$arch download-toolchain; \
done && \
for arch in amd64 arm64 386 arm mipsle riscv64 loong64; do \
go run ./cmd/build-naive --target=linux/$$arch --libc=musl download-toolchain; \
done
race:
export GOTOOLCHAIN=local && \
go build -race $(MAIN_PARAMS) $(MAIN)
@@ -61,17 +36,23 @@ install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
fmt:
@golangci-lint fmt
@gofumpt -l -w .
@gofmt -s -w .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_docs:
go run ./cmd/internal/format_docs
fmt_install:
go install -v mvdan.cc/gofumpt@latest
go install -v github.com/daixiang0/gci@latest
lint:
GOOS=linux golangci-lint run ./...
GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./...
# GOOS=freebsd golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
@@ -88,16 +69,13 @@ proto_install:
update_certificates:
go run ./cmd/internal/update_certificates
release: build_admin_panel build_naive
CRONET_GO_PATH=$(CRONET_GO_PATH) go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
release:
go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
mkdir dist/release
mv dist/*.tar.gz \
dist/*.zip \
dist/*.ipk \
dist/*.apk \
dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
rm -r dist/release
release_repo:
@@ -106,15 +84,6 @@ release_repo:
release_install:
go install -v github.com/tcnksm/ghr@latest
release_docker:
sudo docker buildx build \
--platform $(DOCKER_PLATFORMS) \
-t $(DOCKER_IMAGE):latest \
-t $(DOCKER_IMAGE):$(VERSION) \
--push \
--network=host \
.
update_android_version:
go run ./cmd/internal/update_android_version
@@ -124,14 +93,11 @@ build_android:
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
VERSION_CODE=$$(grep VERSION_CODE ../sing-box-for-android/version.properties | cut -d= -f2); \
VERSION_NAME=$$(grep VERSION_NAME ../sing-box-for-android/version.properties | cut -d= -f2); \
printf '{\n "version_code": %s,\n "version_name": "%s"\n}\n' "$$VERSION_CODE" "$$VERSION_NAME" > dist/release_android/SFA-version-metadata.json
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
rm -rf dist/release_android
release_android: build_android upload_android
release_android: lib_android update_android_version build_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
@@ -204,31 +170,14 @@ upload_macos_pkg:
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
replace_macos_pkg:
mkdir -p dist/SFM
cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg"
cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg"
cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"
upload_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
replace_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"
release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
replace_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms
build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \

View File

@@ -1,56 +1,36 @@
# sing-box-extended
[![license](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)
[![go](https://img.shields.io/badge/go-1.26-00ADD8.svg)](go.mod)
[![codeberg](https://img.shields.io/badge/mirror-codeberg-2185D0.svg)](https://codeberg.org/shtorm-7/sing-box-extended)
[![telegram](https://img.shields.io/badge/telegram-channel-26A5E4.svg)](https://t.me/sing_box_extended)
Sing-box with extended features.
## 🔥 Features
### Protocols
### 🌐 Outbounds
- **WARP** — Cloudflare WARP integration through WireGuard
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
- **Tunnel** — Protocol for creating tunnels across nodes
- **Bond** — Link aggregation for increased throughput
- **Mieru** — Secure, hard to classify, hard to probe network protocol
- **OpenVPN** — OpenVPN client with tls-auth, tls-crypt and tls-crypt-v2 support
- **TrustTunnel** — AdGuard's obfuscated VPN protocol, indistinguishable from HTTPS traffic
- **Sudoku** — Traffic obfuscation protocol based on 4×4 Sudoku puzzles with low-entropy fingerprints
- **SSH** — SSH client and server with certificate authentication and upstream fallback
- **VPN** — Routed tunnel over any TCP sing-box protocol
- **Bond** — Link aggregation for increasing throughput
- **Fallback** — Outbound group with priority-based switching
- **Failover** — Automatic outbound switching with session recovery for high availability
- **Failover** — Automatic outbound switching for high availability
### DNS
- **SDNS (DNSCrypt)** — Encrypted DNS queries for enhanced privacy
- **DNS Fallback** — Sequential / parallel queries across upstream resolvers
### Limiters
- **Bandwidth Limiter** — Upload / download / bidirectional rate limiting
### 🚦 Limiters
- **Bandwidth Limiter** — Upload / download rate limiting
- **Connection Limiter** — Concurrent connection control
- **Traffic Limiter** — Per-user traffic quotas
- **Rate Limiter** — Request rate limiting
### Encryption & Obfuscation
- **Amnezia 2.0** — WireGuard traffic obfuscation
### 🛡 Encryption & Obfuscation
- **Amnezia 1.5** — WireGuard traffic obfuscation
- **VLESS encryption** — XRAY encryption for VLESS protocol
### Transports
### 🔄 Transports
- **mKCP** — Reliable UDP-based transport
- **XHTTP** — Modern XRAY transport
### Services
### 🛠 Services
- **Admin Panel** — Web-based management interface
- **Manager** — Management service for configuring users, nodes, limiters
- **Manager API (HTTP/gRPC)** — HTTP and gRPC API for the Manager
- **Node Manager API** — Service for connecting nodes to remote manager
- **Manager** — Management service for configuring squads, nodes, users, limiters
- **Node Manager** — Service for connecting nodes to remote manager
### Miscellaneous
- **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC)
- **Extended WireGuard options** — Advanced configuration capabilities
### Miscellaneous
- **SDNS (DNSCrypt)** — Encrypted DNS queries for enhanced privacy
- **Extended WireGuard options** — Advanced configuration capabilities
- **Unified Delay** — Unified latency measurement
## 📚 Examples
@@ -63,36 +43,28 @@ https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
If you want to support the project, you can donate to the following addresses.
#### Tribute
**[RUB Donate](https://web.tribute.tg/d/JxY)**
**[EUR Donate](https://web.tribute.tg/d/JxZ)**
**[USD Donate](https://web.tribute.tg/d/Jy1)**
#### TRX (Tron)
### TRX (Tron)
```
TSWU6VUZ4FcUghYDmbbhK15gRVvhvBgW3F
```
#### TON
### TON
```
UQAyD2UuT5kCP6lZQlhFL0hyNibDXNE4nIo_RSLVSYAtD7N1
```
#### Solana
### Solana
```
CJu8ickwRCwNE71uVFjYf1UveyCkRp9Xo44rhPcQpeFL
```
#### Bitcoin
### Bitcoin
```
bc1qqx97p8k4dchqkyd47s4vf74hrqdfnmhqvcja7x
```
#### Ethereum
### Ethereum
```
0xAcc5919C22F2B3fAa0ec7E8BaD142da5B375FBF6
```
## License
## 📄 License
```
Copyright (C) 2022 by nekohasekai <contact-sagernet@sekai.icu>

View File

@@ -68,8 +68,6 @@ type DNSTransport interface {
Type() string
Tag() string
Dependencies() []string
// Reset closes the transport's existing connections so later requests use fresh connections.
// Exchanges that are currently using those connections may fail.
Reset()
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -11,6 +12,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.EndpointManager = (*Manager)(nil)
@@ -49,12 +51,13 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock()
for _, endpoint := range endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(endpoint, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -72,13 +75,14 @@ func (m *Manager) Close() error {
var err error
for _, endpoint := range endpoints {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
done := adapter.LogElapsed(m.logger, "close ", name)
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
done()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -129,12 +133,13 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if m.started {
name := "endpoint/" + endpoint.Type() + "[" + endpoint.Tag() + "]"
for _, stage := range adapter.ListStartStages {
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(endpoint, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {

View File

@@ -48,7 +48,6 @@ type CacheFile interface {
RDRCStore
StoreWARPConfig() bool
StoreMASQUEConfig() bool
LoadMode() string
StoreMode(mode string) error
@@ -60,10 +59,6 @@ type CacheFile interface {
SaveRuleSet(tag string, set *SavedBinary) error
LoadWARPConfig(tag string) *SavedBinary
SaveWARPConfig(tag string, set *SavedBinary) error
LoadMASQUEConfig(tag string) *SavedBinary
SaveMASQUEConfig(tag string, set *SavedBinary) error
LoadSubscription(tag string) *SavedBinary
SaveSubscription(tag string, sub *SavedBinary) error
}
type SavedBinary struct {

View File

@@ -42,15 +42,16 @@ type InboundManager interface {
}
type InboundContext struct {
Inbound string
InboundType string
IPVersion uint8
Network string
Source M.Socksaddr
Destination M.Socksaddr
Gateway *netip.Addr
User string
Outbound string
Inbound string
InboundType string
IPVersion uint8
Network string
Source M.Socksaddr
Destination M.Socksaddr
TunnelSource string
TunnelDestination string
User string
Outbound string
// sniffer
@@ -103,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

@@ -4,6 +4,7 @@ import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -11,6 +12,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.InboundManager = (*Manager)(nil)
@@ -46,12 +48,13 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock()
for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(inbound, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -69,13 +72,14 @@ func (m *Manager) Close() error {
var err error
for _, inbound := range inbounds {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
done := adapter.LogElapsed(m.logger, "close ", name)
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, inbound.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
done()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -129,12 +133,13 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if m.started {
name := "inbound/" + inbound.Type() + "[" + inbound.Tag() + "]"
for _, stage := range adapter.ListStartStages {
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(inbound, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsInbound, loaded := m.inboundByTag[tag]; loaded {

View File

@@ -77,38 +77,26 @@ func getServiceName(service any) string {
func Start(logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
for _, service := range services {
name := getServiceName(service)
done := LogElapsed(logger, stage, " ", name)
logger.Trace(stage, " ", name)
startTime := time.Now()
err := service.Start(stage)
done()
if err != nil {
return err
}
logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func StartNamed(logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
for _, service := range services {
done := LogElapsed(logger, stage, " ", service.Name())
logger.Trace(stage, " ", service.Name())
startTime := time.Now()
err := service.Start(stage)
done()
if err != nil {
return E.Cause(err, stage.String(), " ", service.Name())
}
logger.Trace(stage, " ", service.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func LogElapsed(logger log.ContextLogger, description ...any) func() {
prefix := F.ToString(description...)
startTime := time.Now()
timer := time.AfterFunc(time.Second, func() {
logger.Trace(prefix, "...")
})
return func() {
if timer.Stop() {
return
}
logger.Trace(prefix, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}

View File

@@ -1,9 +1,6 @@
package adapter
import (
"encoding/hex"
"net"
"strings"
"time"
C "github.com/sagernet/sing-box/constant"
@@ -54,24 +51,6 @@ type WIFIState struct {
BSSID string
}
func NormalizeWIFIBSSID(bssid string) string {
bssid = strings.TrimSpace(bssid)
if bssid == "" {
return ""
}
parsed, err := net.ParseMAC(bssid)
if err == nil && len(parsed) == 6 {
return parsed.String()
}
if len(bssid) == 12 {
decoded, err := hex.DecodeString(bssid)
if err == nil {
return net.HardwareAddr(decoded).String()
}
}
return bssid
}
type NetworkInterface struct {
control.Interface
Type C.InterfaceType

View File

@@ -6,6 +6,7 @@ import (
"os"
"strings"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -13,6 +14,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
)
@@ -82,12 +84,13 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock()
for _, outbound := range outbounds {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(outbound, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
return nil
@@ -114,25 +117,27 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
canContinue = true
name := "outbound/" + outboundToStart.Type() + "[" + outboundTag + "]"
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
done := adapter.LogElapsed(m.logger, "start ", name)
m.logger.Trace("start ", name)
startTime := time.Now()
monitor.Start("start ", name)
err := starter.Start(adapter.StartStateStart)
monitor.Finish()
done()
if err != nil {
return E.Cause(err, "start ", name)
}
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
} else if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
done := adapter.LogElapsed(m.logger, "start ", name)
m.logger.Trace("start ", name)
startTime := time.Now()
monitor.Start("start ", name)
err := starter.Start()
monitor.Finish()
done()
if err != nil {
return E.Cause(err, "start ", name)
}
m.logger.Trace("start ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if len(started) == len(outbounds) {
@@ -180,13 +185,14 @@ func (m *Manager) Close() error {
for _, outbound := range outbounds {
if closer, isCloser := outbound.(io.Closer); isCloser {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
done := adapter.LogElapsed(m.logger, "close ", name)
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
done()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
return nil
@@ -233,7 +239,7 @@ func (m *Manager) Remove(tag string) error {
if m.defaultOutbound == outbound {
if len(m.outbounds) > 0 {
m.defaultOutbound = m.outbounds[0]
m.logger.Notice("updated default outbound to ", m.defaultOutbound.Tag())
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
} else {
m.defaultOutbound = nil
}
@@ -269,12 +275,13 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if m.started {
name := "outbound/" + outbound.Type() + "[" + outbound.Tag() + "]"
for _, stage := range adapter.ListStartStages {
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(outbound, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
m.access.Lock()
@@ -303,7 +310,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
m.defaultOutbound = outbound
if m.started {
m.logger.Notice("updated default outbound to ", outbound.Tag())
m.logger.Info("updated default outbound to ", outbound.Tag())
}
}
return nil

View File

@@ -1,8 +1,6 @@
package adapter
import (
"net/netip"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
@@ -13,7 +11,6 @@ type PlatformInterface interface {
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error
BindInterfaceControl(fd int, interfaceName string) error
UsePlatformInterface() bool
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
@@ -39,8 +36,6 @@ type PlatformInterface interface {
UsePlatformNotification() bool
SendNotification(notification *Notification) error
MyInterfaceAddress() []netip.Addr
}
type FindConnectionOwnerRequest struct {
@@ -52,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 {

View File

@@ -1,51 +0,0 @@
package adapter
import (
"context"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/x/list"
)
type Provider interface {
Type() string
Tag() string
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
UpdatedAt() time.Time
HealthCheck(ctx context.Context) (map[string]uint16, error)
RegisterCallback(callback ProviderUpdateCallback) *list.Element[ProviderUpdateCallback]
UnregisterCallback(element *list.Element[ProviderUpdateCallback])
}
type ProviderUpdater interface {
Update() error
}
type ProviderSubscriptionInfo interface {
SubscriptionInfo() SubscriptionInfo
}
type ProviderRegistry interface {
option.ProviderOptionsRegistry
CreateProvider(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) (Provider, error)
}
type ProviderManager interface {
Lifecycle
Providers() []Provider
Get(tag string) (Provider, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logFactory log.Factory, tag string, providerType string, options any) error
}
type SubscriptionInfo struct {
Upload int64
Download int64
Total int64
Expire int64
}
type ProviderUpdateCallback = func(tag string) error

View File

@@ -1,309 +0,0 @@
package provider
import (
"context"
"reflect"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
)
type Adapter struct {
ctx context.Context
outbound adapter.OutboundManager
router adapter.Router
logFactory log.Factory
logger log.ContextLogger
providerType string
providerTag string
outbounds []adapter.Outbound
outboundsByTag map[string]adapter.Outbound
ticker *time.Ticker
checking atomic.Bool
history adapter.URLTestHistoryStorage
callbackAccess sync.Mutex
callbacks list.List[adapter.ProviderUpdateCallback]
link string
enabled bool
removeEmojis bool
timeout time.Duration
interval time.Duration
}
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
timeout := time.Duration(options.Timeout)
if timeout == 0 {
timeout = 3 * time.Second
}
interval := time.Duration(options.Interval)
if interval == 0 {
interval = 10 * time.Minute
}
if interval < time.Minute {
interval = time.Minute
}
return Adapter{
ctx: ctx,
outbound: outbound,
router: router,
logFactory: logFactory,
logger: logger,
providerType: providerType,
providerTag: providerTag,
enabled: options.Enabled,
link: options.URL,
timeout: timeout,
interval: interval,
}
}
func (a *Adapter) SetRemoveEmojis(remove bool) {
a.removeEmojis = remove
}
func (a *Adapter) Start() error {
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
if a.history == nil {
if clashServer := service.FromContext[adapter.ClashServer](a.ctx); clashServer != nil {
a.history = clashServer.HistoryStorage()
} else {
a.history = urltest.NewHistoryStorage()
}
}
go a.loopCheck()
return nil
}
func (a *Adapter) Type() string {
return a.providerType
}
func (a *Adapter) Tag() string {
return a.providerTag
}
func (a *Adapter) Outbounds() []adapter.Outbound {
return a.outbounds
}
func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
if a.outboundsByTag == nil {
return nil, false
}
detour, ok := a.outboundsByTag[tag]
return detour, ok
}
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
if a.removeEmojis {
removeEmojisFromTags(newOpts)
}
uniquifyTags(newOpts)
a.removeUseless(newOpts)
var (
oldOptByTag = make(map[string]option.Outbound)
outbounds = make([]adapter.Outbound, 0, len(newOpts))
outboundsByTag = make(map[string]adapter.Outbound)
)
for _, opt := range oldOpts {
oldOptByTag[opt.Tag] = opt
}
for i, opt := range newOpts {
var tag string
if opt.Tag != "" {
tag = F.ToString(a.providerTag, "/", opt.Tag)
} else {
tag = F.ToString(a.providerTag, "/", i)
}
outbound, exist := a.outbound.Outbound(tag)
if !exist || !reflect.DeepEqual(opt, oldOptByTag[opt.Tag]) {
err := a.outbound.Create(
adapter.WithContext(a.ctx, &adapter.InboundContext{
Outbound: tag,
}),
a.router,
a.logFactory.NewLogger(F.ToString("outbound/", opt.Type, "[", tag, "]")),
tag,
opt.Type,
opt.Options,
)
if err != nil {
a.logger.Warn(err, " in ", tag, ", skip create this outbound")
continue
}
outbound, _ = a.outbound.Outbound(tag)
}
outbounds = append(outbounds, outbound)
outboundsByTag[tag] = outbound
}
if a.enabled && a.history != nil {
go a.HealthCheck(a.ctx)
}
a.outbounds = outbounds
a.outboundsByTag = outboundsByTag
}
func (a *Adapter) HealthCheck(ctx context.Context) (map[string]uint16, error) {
if a.ticker != nil {
a.ticker.Reset(a.interval)
}
return a.healthcheck(ctx)
}
func (a *Adapter) RegisterCallback(callback adapter.ProviderUpdateCallback) *list.Element[adapter.ProviderUpdateCallback] {
a.callbackAccess.Lock()
defer a.callbackAccess.Unlock()
return a.callbacks.PushBack(callback)
}
func (a *Adapter) UnregisterCallback(element *list.Element[adapter.ProviderUpdateCallback]) {
a.callbackAccess.Lock()
defer a.callbackAccess.Unlock()
a.callbacks.Remove(element)
}
func (a *Adapter) UpdateGroups() {
for element := a.callbacks.Front(); element != nil; element = element.Next() {
element.Value(a.providerTag)
}
}
func (a *Adapter) Close() error {
if a.ticker != nil {
a.ticker.Stop()
}
outbounds := a.outbounds
a.outbounds = nil
var err error
for _, ob := range outbounds {
if err2 := a.outbound.Remove(ob.Tag()); err2 != nil {
err = E.Append(err, err2, func(err error) error {
return E.Cause(err, "close outbound [", ob.Tag(), "]")
})
}
}
return err
}
func (a *Adapter) loopCheck() {
if !a.enabled {
return
}
a.ticker = time.NewTicker(a.interval)
a.healthcheck(a.ctx)
for {
select {
case <-a.ctx.Done():
return
case <-a.ticker.C:
a.healthcheck(a.ctx)
}
}
}
func (a *Adapter) healthcheck(ctx context.Context) (map[string]uint16, error) {
result := make(map[string]uint16)
if a.checking.Swap(true) {
return result, nil
}
defer a.checking.Store(false)
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
var resultAccess sync.Mutex
checked := make(map[string]bool)
for _, detour := range a.outbounds {
tag := detour.Tag()
if checked[tag] {
continue
}
checked[tag] = true
b.Go(tag, func() (any, error) {
ctx, cancel := context.WithTimeout(a.ctx, a.timeout)
defer cancel()
t, err := urltest.URLTest(ctx, a.link, detour)
if err != nil {
a.logger.Debug("outbound ", tag, " unavailable: ", err)
a.history.DeleteURLTestHistory(tag)
} else {
a.logger.Debug("outbound ", tag, " available: ", t, "ms")
a.history.StoreURLTestHistory(tag, &adapter.URLTestHistory{
Time: time.Now(),
Delay: t,
})
resultAccess.Lock()
result[tag] = t
resultAccess.Unlock()
}
return nil, nil
})
}
b.Wait()
return result, nil
}
func (a *Adapter) removeUseless(newOpts []option.Outbound) {
if len(a.outbounds) == 0 {
return
}
exists := make(map[string]bool)
for i, opt := range newOpts {
var tag string
if opt.Tag != "" {
tag = F.ToString(a.providerTag, "/", opt.Tag)
} else {
tag = F.ToString(a.providerTag, "/", i)
}
exists[tag] = true
}
for _, opt := range a.outbounds {
if !exists[opt.Tag()] {
if err := a.outbound.Remove(opt.Tag()); err != nil {
a.logger.Error(err, "close outbound [", opt.Tag(), "]")
}
}
}
}
func uniquifyTags(opts []option.Outbound) {
count := make(map[string]int)
for i, opt := range opts {
count[opt.Tag]++
if count[opt.Tag] > 1 {
opts[i].Tag = F.ToString(opt.Tag, " #", count[opt.Tag])
}
}
}
func removeEmojisFromTags(opts []option.Outbound) {
for i, opt := range opts {
cleaned := flagRegex.ReplaceAllStringFunc(opt.Tag, flagToCountryCode)
cleaned = emojiRegex.ReplaceAllString(cleaned, "")
cleaned = multiSpaceRegex.ReplaceAllString(cleaned, " ")
opts[i].Tag = strings.TrimSpace(cleaned)
}
}
func flagToCountryCode(flag string) string {
runes := []rune(flag)
if len(runes) == 2 {
return string(rune(runes[0]-0x1F1E6+'A')) + string(rune(runes[1]-0x1F1E6+'A')) + " "
}
return ""
}
var flagRegex = regexp.MustCompile(`[\x{1F1E6}-\x{1F1FF}]{2}`)
var emojiRegex = regexp.MustCompile(`[\x{1F1E0}-\x{1F1FF}\x{1F300}-\x{1F9FF}\x{2600}-\x{27BF}\x{FE00}-\x{FE0F}\x{200D}]+`)
var multiSpaceRegex = regexp.MustCompile(`\s{2,}`)

View File

@@ -1,45 +0,0 @@
package provider
import (
"testing"
"github.com/sagernet/sing-box/option"
)
func TestFlagToCountryCodeAllFlags(t *testing.T) {
for first := 'A'; first <= 'Z'; first++ {
for second := 'A'; second <= 'Z'; second++ {
flag := string(rune(0x1F1E6+(first-'A'))) + string(rune(0x1F1E6+(second-'A')))
expected := string(first) + string(second)
result := flagToCountryCode(flag)
// flagToCountryCode appends a space
if result != expected+" " {
t.Errorf("flagToCountryCode(%q) = %q, want %q", expected, result, expected+" ")
}
}
}
}
func TestRemoveEmojisFromTags(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"🇺🇸 United States", "US United States"},
{"🇷🇺 Россия", "RU Россия"},
{"🇩🇪 Germany 🚀", "DE Germany"},
{"🇫🇷🇬🇧 France-UK", "FR GB France-UK"},
{"No emojis here", "No emojis here"},
{"🌍 World", "World"},
{"🇯🇵 Tokyo ⚡ Fast", "JP Tokyo Fast"},
{"Germany 🇩🇪", "Germany DE"},
{"Server 🇺🇸 Node", "Server US Node"},
}
for _, tt := range tests {
opts := []option.Outbound{{Tag: tt.input}}
removeEmojisFromTags(opts)
if opts[0].Tag != tt.expected {
t.Errorf("removeEmojisFromTags(%q) = %q, want %q", tt.input, opts[0].Tag, tt.expected)
}
}
}

View File

@@ -1,157 +0,0 @@
package provider
import (
"context"
"io"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
var _ adapter.ProviderManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.ProviderRegistry
access sync.Mutex
started bool
stage adapter.StartStage
providers []adapter.Provider
providerByTag map[string]adapter.Provider
wg sync.WaitGroup
}
func NewManager(logger logger.ContextLogger, registry adapter.ProviderRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
providerByTag: make(map[string]adapter.Provider),
}
}
func (m *Manager) Initialize() {
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
providers := m.providers
m.access.Unlock()
for _, provider := range providers {
err := adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
monitor := taskmonitor.New(m.logger, C.StopTimeout)
m.access.Lock()
if !m.started {
m.access.Unlock()
return nil
}
m.started = false
providers := m.providers
m.providers = nil
m.access.Unlock()
var err error
for _, provider := range providers {
if closer, isCloser := provider.(io.Closer); isCloser {
monitor.Start("close provider/", provider.Type(), "[", provider.Tag(), "]")
err = E.Append(err, closer.Close(), func(err error) error {
return E.Cause(err, "close provider/", provider.Type(), "[", provider.Tag(), "]")
})
monitor.Finish()
}
}
return nil
}
func (m *Manager) Providers() []adapter.Provider {
m.access.Lock()
defer m.access.Unlock()
return m.providers
}
func (m *Manager) Get(tag string) (adapter.Provider, bool) {
m.access.Lock()
provider, found := m.providerByTag[tag]
m.access.Unlock()
return provider, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
provider, found := m.providerByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.providerByTag, tag)
index := common.Index(m.providers, func(it adapter.Provider) bool {
return it == provider
})
if index == -1 {
panic("invalid provider index")
}
m.providers = append(m.providers[:index], m.providers[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return common.Close(provider)
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) error {
if tag == "" {
return os.ErrInvalid
}
provider, err := m.registry.CreateProvider(ctx, router, logFactory, tag, providerType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " provider/", provider.Type(), "[", provider.Tag(), "]")
}
}
}
if existsProvider, loaded := m.providerByTag[tag]; loaded {
if m.started {
err = common.Close(existsProvider)
if err != nil {
return E.Cause(err, "close provider", provider.Type(), "[", existsProvider.Tag(), "]")
}
}
existsIndex := common.Index(m.providers, func(it adapter.Provider) bool {
return it == existsProvider
})
if existsIndex == -1 {
panic("invalid provider index")
}
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
}
m.providers = append(m.providers, provider)
m.providerByTag[tag] = provider
return nil
}

View File

@@ -1,72 +0,0 @@
package provider
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options T) (adapter.Provider, error)
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
registry.register(providerType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, rawOptions any) (adapter.Provider, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logFactory, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.ProviderRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options any) (adapter.Provider, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(providerType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[providerType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateProvider(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, providerType string, options any) (adapter.Provider, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[providerType]
if !loaded {
return nil, E.New("provider type not found: '" + providerType + "'")
}
return constructor(ctx, router, logFactory, tag, options)
}
func (r *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[providerType] = optionsConstructor
r.constructors[providerType] = constructor
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -11,6 +12,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.ServiceManager = (*Manager)(nil)
@@ -44,12 +46,13 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Unlock()
for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(service, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -67,13 +70,14 @@ func (m *Manager) Close() error {
var err error
for _, service := range services {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
done := adapter.LogElapsed(m.logger, "close ", name)
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, service.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
done()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
@@ -124,12 +128,13 @@ func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag stri
if m.started {
name := "service/" + service.Type() + "[" + service.Tag() + "]"
for _, stage := range adapter.ListStartStages {
done := adapter.LogElapsed(m.logger, stage, " ", name)
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(service, stage)
done()
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsService, loaded := m.serviceByTag[tag]; loaded {

76
box.go
View File

@@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/adapter/provider"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/certificate"
"github.com/sagernet/sing-box/common/dialer"
@@ -21,6 +20,7 @@ import (
"github.com/sagernet/sing-box/common/urltest"
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"
@@ -45,7 +45,6 @@ type Box struct {
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
provider *provider.Manager
service *boxService.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
@@ -66,7 +65,6 @@ func Context(
inboundRegistry adapter.InboundRegistry,
outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry,
providerRegistry adapter.ProviderRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry,
) context.Context {
@@ -85,11 +83,6 @@ func Context(
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
}
if service.FromContext[option.ProviderOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.ProviderRegistry](ctx) == nil {
ctx = service.ContextWith[option.ProviderOptionsRegistry](ctx, providerRegistry)
ctx = service.ContextWith[adapter.ProviderRegistry](ctx, providerRegistry)
}
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
@@ -112,7 +105,6 @@ func New(options Options) (*Box, error) {
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
providerRegistry := service.FromContext[adapter.ProviderRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
@@ -125,9 +117,6 @@ func New(options Options) (*Box, error) {
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
if providerRegistry == nil {
return nil, E.New("missing provider registry in context")
}
if dnsTransportRegistry == nil {
return nil, E.New("missing DNS transport registry in context")
}
@@ -193,13 +182,11 @@ func New(options Options) (*Box, error) {
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
providerManager := provider.NewManager(logFactory.NewLogger("provider"), providerRegistry)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.ProviderManager](ctx, providerManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
@@ -290,10 +277,6 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
}
options.Outbounds = append(options.Outbounds, option.Outbound{
Tag: "Compatible",
Type: C.TypeDirect,
})
for i, outboundOptions := range options.Outbounds {
var tag string
if outboundOptions.Tag != "" {
@@ -320,25 +303,6 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize outbound[", i, "]")
}
}
for i, providerOptions := range options.Providers {
var tag string
if providerOptions.Tag != "" {
tag = providerOptions.Tag
} else {
tag = F.ToString(i)
}
err = providerManager.Create(
ctx,
router,
logFactory,
tag,
providerOptions.Type,
providerOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize provider[", i, "]")
}
}
for i, serviceOptions := range options.Services {
var tag string
if serviceOptions.Tag != "" {
@@ -367,12 +331,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 {
@@ -429,7 +392,6 @@ func New(options Options) (*Box, error) {
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
provider: providerManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
dnsRouter: dnsRouter,
@@ -458,7 +420,7 @@ func (s *Box) PreStart() error {
s.Close()
return err
}
s.logger.Notice("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
@@ -477,7 +439,7 @@ func (s *Box) Start() error {
s.Close()
return err
}
s.logger.Notice("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
@@ -493,11 +455,11 @@ func (s *Box) preStart() error {
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.provider, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
@@ -517,7 +479,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.provider, s.service)
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
@@ -525,7 +487,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.provider, s.service)
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
@@ -551,7 +513,6 @@ func (s *Box) Close() error {
{"service", s.service},
{"endpoint", s.endpoint},
{"inbound", s.inbound},
{"provider", s.provider},
{"outbound", s.outbound},
{"router", s.router},
{"connection", s.connection},
@@ -559,24 +520,27 @@ func (s *Box) Close() error {
{"dns-transport", s.dnsTransport},
{"network", s.network},
} {
done := adapter.LogElapsed(s.logger, "close ", closeItem.name)
s.logger.Trace("close ", closeItem.name)
startTime := time.Now()
err = E.Append(err, closeItem.service.Close(), func(err error) error {
return E.Cause(err, "close ", closeItem.name)
})
done()
s.logger.Trace("close ", closeItem.name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
for _, lifecycleService := range s.internalService {
done := adapter.LogElapsed(s.logger, "close ", lifecycleService.Name())
s.logger.Trace("close ", lifecycleService.Name())
startTime := time.Now()
err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name())
})
done()
s.logger.Trace("close ", lifecycleService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
done := adapter.LogElapsed(s.logger, "close logger")
s.logger.Trace("close logger")
startTime := time.Now()
err = E.Append(err, s.logFactory.Close(), func(err error) error {
return E.Cause(err, "close logger")
})
done()
s.logger.Trace("close logger completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
return err
}
@@ -596,10 +560,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

@@ -1,194 +0,0 @@
// Command admin_panel_pack post-processes a directory of built SPA
// assets so it can be served straight from the Go binary via //go:embed.
// It does *not* generate any Go source any more — that responsibility
// moved to the embed directive in service/admin_panel/service.go.
//
// Three transformations happen here, all in-place inside the supplied
// directory:
//
// 1. Legacy WOFF (1.0) fallback fonts are deleted. Every browser made
// after 2014 reads WOFF2 natively, so shipping both formats roughly
// doubles the embedded font payload for no real-world benefit. The
// matching `,url(*.woff) format("woff")` segments are stripped from
// the bundled CSS in step (2) so the @font-face rules don't reference
// files that aren't shipped.
// 2. Bundled CSS is rewritten to drop those WOFF URL fragments.
// 3. Compressible text assets (.html, .css, .js, .svg, .json, .map) are
// pre-gzipped as companion `*.gz` files. The HTTP handler then either
// passes those bytes through verbatim with Content-Encoding: gzip or
// falls back to the raw file for the rare client that does not
// advertise gzip support — no on-line compression, no surprises.
// Already-compressed formats (.woff2, fonts, images) are skipped: gzip
// can't shrink them and the duplicate would only inflate the binary.
package main
import (
"bytes"
"compress/gzip"
"flag"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
func main() {
dir := flag.String("dir", "service/admin_panel/dist", "directory of built SPA assets to post-process in place")
flag.Parse()
woffDropped, err := pruneWoff(*dir)
if err != nil {
fail("prune woff: %v", err)
}
cssRewritten, err := rewriteCSS(*dir)
if err != nil {
fail("rewrite css: %v", err)
}
stats, err := gzipText(*dir)
if err != nil {
fail("gzip text: %v", err)
}
fmt.Fprintf(os.Stderr, "post-processed %s: dropped %d woff, rewrote %d css, gzipped %d files (%d→%d bytes)\n",
*dir, woffDropped, cssRewritten, stats.gzipped, stats.totalRaw, stats.totalGz)
}
// pruneWoff deletes every legacy *.woff (WOFF 1.0) font under dir. The
// bundled CSS still references them on entry; rewriteCSS drops those
// references in a separate pass so the two operations stay independently
// testable.
func pruneWoff(dir string) (int, error) {
var n int
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() || !strings.EqualFold(filepath.Ext(p), ".woff") {
return nil
}
if err := os.Remove(p); err != nil {
return err
}
n++
return nil
})
return n, err
}
// woffRefAfterRE / woffRefBeforeRE match a single WOFF 1.0 entry inside a
// Vite-bundled CSS `src:` declaration. Vite minifies the rule to
// `src:url(./X.woff2) format("woff2"),url(./X.woff) format("woff");` so the
// "after" regex (the common case) eats the *leading* comma + woff entry,
// leaving only the woff2 source. We also handle the rare reverse ordering
// in a second pass.
var (
woffRefAfterRE = regexp.MustCompile(`,\s*url\([^)]*\.woff\)\s*format\(["']woff["']\)`)
woffRefBeforeRE = regexp.MustCompile(`url\([^)]*\.woff\)\s*format\(["']woff["']\)\s*,\s*`)
)
// rewriteCSS drops every reference to a *.woff URL from every *.css file
// under dir. Pairs naturally with pruneWoff: after both passes, no font
// URL in the bundle points at a file that isn't shipped.
func rewriteCSS(dir string) (int, error) {
var n int
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() || !strings.EqualFold(filepath.Ext(p), ".css") {
return nil
}
data, err := os.ReadFile(p)
if err != nil {
return err
}
out := woffRefAfterRE.ReplaceAll(data, nil)
out = woffRefBeforeRE.ReplaceAll(out, nil)
if bytes.Equal(out, data) {
return nil
}
if err := os.WriteFile(p, out, 0o644); err != nil {
return err
}
n++
return nil
})
return n, err
}
// gzipExts is the set of file extensions for which a `.gz` companion is
// generated. Anything not on this list is left alone — woff2/png/jpeg/etc.
// are already compressed, so gzip can only inflate them slightly while
// doubling the embedded payload.
var gzipExts = map[string]bool{
".html": true,
".css": true,
".js": true,
".mjs": true,
".svg": true,
".json": true,
".map": true,
".txt": true,
".xml": true,
".wasm": true,
}
type gzipStats struct {
gzipped int
totalRaw int64
totalGz int64
}
// gzipText produces a `<file>.gz` companion next to every text-like asset
// in dir, using gzip.BestCompression. The companion is dropped if the
// compressed bytes don't save at least 10 % over the raw file — same
// heuristic we used in the previous (Go-source-emitting) generation, just
// applied to disk files now.
func gzipText(dir string) (gzipStats, error) {
var stats gzipStats
err := filepath.WalkDir(dir, func(p string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if d.IsDir() {
return nil
}
ext := strings.ToLower(filepath.Ext(p))
if ext == ".gz" || !gzipExts[ext] {
return nil
}
raw, err := os.ReadFile(p)
if err != nil {
return err
}
var buf bytes.Buffer
w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
if err != nil {
return err
}
// Reproducible: no mtime, no OS marker.
w.ModTime = time.Time{}
w.OS = 0xff
if _, err := w.Write(raw); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
if buf.Len() > len(raw)*9/10 {
return nil
}
stats.gzipped++
stats.totalRaw += int64(len(raw))
stats.totalGz += int64(buf.Len())
return os.WriteFile(p+".gz", buf.Bytes(), 0o644)
})
return stats, err
}
func fail(format string, args ...any) {
fmt.Fprintf(os.Stderr, "admin_panel_pack: "+format+"\n", args...)
os.Exit(1)
}

View File

@@ -63,7 +63,7 @@ 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_masque", "with_mtproxy", "with_trusttunnel", "with_openvpn", "with_sudoku", "with_snell", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_naive_outbound", "with_clash_api", "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")

View File

@@ -48,8 +48,8 @@ func GetRuntimeEnv(key string) (string, error) {
if readErr != nil {
return "", readErr
}
envStrings := strings.SplitSeq(string(data), "\n")
for envItem := range envStrings {
envStrings := strings.Split(string(data), "\n")
for _, envItem := range envStrings {
envItem = strings.TrimSuffix(envItem, "\r")
envKeyValue := strings.Split(envItem, "=")
if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {

View File

@@ -39,7 +39,7 @@ func main() {
common.Must(os.Chdir(androidPath))
localProps := common.Must1(os.ReadFile("version.properties"))
var propsList [][]string
for propLine := range strings.SplitSeq(string(localProps), "\n") {
for _, propLine := range strings.Split(string(localProps), "\n") {
propsList = append(propsList, strings.Split(propLine, "="))
}
var (

View File

@@ -45,8 +45,10 @@ package certificate
import "crypto/x509"
func newMozillaIncluded() *x509.CertPool {
pool := x509.NewCertPool()
var mozillaIncluded *x509.CertPool
func init() {
mozillaIncluded = x509.NewCertPool()
`)
for {
record, err := reader.Read()
@@ -61,14 +63,14 @@ func newMozillaIncluded() *x509.CertPool {
generated.WriteString("\n // ")
generated.WriteString(record[nameIndex])
generated.WriteString("\n")
generated.WriteString(" pool.AppendCertsFromPEM([]byte(`")
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
cert := record[certIndex]
// Remove single quotes
cert = cert[1 : len(cert)-1]
generated.WriteString(cert)
generated.WriteString("`))\n")
}
generated.WriteString("\treturn pool\n}\n")
generated.WriteString("}\n")
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
}
@@ -129,8 +131,10 @@ package certificate
import "crypto/x509"
func newChromeIncluded() *x509.CertPool {
pool := x509.NewCertPool()
var chromeIncluded *x509.CertPool
func init() {
chromeIncluded = x509.NewCertPool()
`)
for {
record, err := reader.Read()
@@ -148,7 +152,7 @@ func newChromeIncluded() *x509.CertPool {
generated.WriteString("\n // ")
generated.WriteString(record[subjectIndex])
generated.WriteString("\n")
generated.WriteString(" pool.AppendCertsFromPEM([]byte(`")
generated.WriteString(" chromeIncluded.AppendCertsFromPEM([]byte(`")
cert := record[certIndex]
// Remove single quotes if present
if len(cert) > 0 && cert[0] == '\'' {
@@ -157,6 +161,6 @@ func newChromeIncluded() *x509.CertPool {
generated.WriteString(cert)
generated.WriteString("`))\n")
}
generated.WriteString("\treturn pool\n}\n")
generated.WriteString("}\n")
return os.WriteFile("common/certificate/chrome.go", []byte(generated.String()), 0o644)
}

View File

@@ -61,17 +61,16 @@ func geoipExport(countryCode string) error {
outputFile *os.File
outputWriter io.Writer
)
switch flagGeoipExportOutput {
case "stdout":
if flagGeoipExportOutput == "stdout" {
outputWriter = os.Stdout
case flagGeoipExportDefaultOutput:
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
default:
} else {
outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil {
return err

View File

@@ -43,17 +43,16 @@ func geositeExport(category string) error {
outputFile *os.File
outputWriter io.Writer
)
switch commandGeositeExportOutput {
case "stdout":
if commandGeositeExportOutput == "stdout" {
outputWriter = os.Stdout
case commandGeositeExportDefaultOutput:
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
default:
} else {
outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil {
return err

View File

@@ -179,7 +179,6 @@ func run() error {
for {
osSignal := <-osSignals
if osSignal == syscall.SIGHUP {
log.Notice("received SIGHUP, reloading...")
err = check()
if err != nil {
log.Error(E.Cause(err, "reload service"))

View File

@@ -1,5 +1,3 @@
//go:build with_quic
package main
import (

View File

@@ -1,78 +0,0 @@
#!/bin/bash
set -euo pipefail
# Usage: ./codeberg-release.sh [--replace] [--draft] [--prerelease] [-p N] TAG PATH
OWNER="shtorm-7"
REPO="sing-box-extended"
API="https://codeberg.org/api/v1"
TOKEN="${CODEBERG_TOKEN:?Set CODEBERG_TOKEN}"
REPLACE=false
DRAFT=false
PRERELEASE=false
PARALLEL=1
while [[ $# -gt 2 ]]; do
case "$1" in
--replace) REPLACE=true; shift ;;
--draft) DRAFT=true; shift ;;
--prerelease) PRERELEASE=true; shift ;;
-p) PARALLEL="$2"; shift 2 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
TAG="$1"
DIR="$2"
if [[ ! -d "$DIR" ]]; then
echo "Error: $DIR is not a directory"
exit 1
fi
RELEASE_URL="$API/repos/$OWNER/$REPO/releases"
RELEASE_ID=$(curl -s -H "Authorization: token $TOKEN" "$RELEASE_URL/tags/$TAG" | jq -r '.id // empty')
if [[ -n "$RELEASE_ID" && "$REPLACE" == "true" ]]; then
curl -s -H "Authorization: token $TOKEN" "$RELEASE_URL/$RELEASE_ID/assets" | \
jq -r '.[].id' | while read -r aid; do
curl -s -X DELETE -H "Authorization: token $TOKEN" "$RELEASE_URL/$RELEASE_ID/assets/$aid" >/dev/null
done
curl -s -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
"$RELEASE_URL/$RELEASE_ID" \
-d "{\"draft\":$DRAFT,\"prerelease\":$PRERELEASE}" >/dev/null
elif [[ -z "$RELEASE_ID" ]]; then
RELEASE_ID=$(curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
"$RELEASE_URL" \
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":$DRAFT,\"prerelease\":$PRERELEASE}" | jq -r '.id')
fi
if [[ -z "$RELEASE_ID" || "$RELEASE_ID" == "null" ]]; then
echo "Error: failed to get or create release"
exit 1
fi
echo "Release ID: $RELEASE_ID"
echo "Uploading files from $DIR (parallelism: $PARALLEL)..."
export TOKEN RELEASE_URL RELEASE_ID
upload() {
local file="$1"
local name
name=$(basename "$file")
if curl -s -X POST \
-H "Authorization: token $TOKEN" \
"$RELEASE_URL/$RELEASE_ID/assets?name=$name" \
-F "attachment=@$file" >/dev/null; then
echo "$name"
else
echo "$name"
fi
}
export -f upload
find "$DIR" -maxdepth 1 -type f -print0 | xargs -0 -P "$PARALLEL" -I {} bash -c 'upload "$@"' _ {}
echo "Done."

View File

@@ -29,7 +29,7 @@ type RawHalfConn struct {
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
halfConn := &RawHalfConn{
pointer: unsafe.Pointer(rawHalfConn.UnsafeAddr()),
pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()),
methods: methods,
}

View File

@@ -112,7 +112,9 @@ func IsValid(versionName string) bool {
}
func Parse(versionName string) (version Version) {
versionName = strings.TrimPrefix(versionName, "v")
if strings.HasPrefix(versionName, "v") {
versionName = versionName[1:]
}
if strings.Contains(versionName, "-") {
parts := strings.Split(versionName, "-")
versionName = parts[0]

View File

@@ -4,11 +4,13 @@ package certificate
import "crypto/x509"
func newChromeIncluded() *x509.CertPool {
pool := x509.NewCertPool()
var chromeIncluded *x509.CertPool
func init() {
chromeIncluded = x509.NewCertPool()
// CN=Actalis Authentication Root CA; O=Actalis S.p.A./03358520967; L=Milan; C=IT
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
@@ -43,7 +45,7 @@ LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----`))
// CN=TunTrust Root CA; O=Agence Nationale de Certification Electronique; C=TN
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
@@ -78,7 +80,7 @@ d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----`))
// CN=Amazon Root CA 4; O=Amazon; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
@@ -93,7 +95,7 @@ CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
-----END CERTIFICATE-----`))
// CN=Amazon Root CA 1; O=Amazon; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
@@ -115,7 +117,7 @@ rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----`))
// CN=Amazon Root CA 2; O=Amazon; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF
ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL
@@ -148,7 +150,7 @@ n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE
-----END CERTIFICATE-----`))
// CN=Amazon Root CA 3; O=Amazon; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
@@ -162,7 +164,7 @@ YyRIHN8wfdVoOw==
-----END CERTIFICATE-----`))
// CN=Certum Trusted Network CA; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
@@ -186,7 +188,7 @@ VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
-----END CERTIFICATE-----`))
// CN=Certum EC-384 CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw
CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw
JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT
@@ -203,7 +205,7 @@ nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----`))
// CN=Certum Trusted Root CA; OU=Certum Certification Authority; O=Asseco Data Systems S.A.; C=PL
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6
MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV
@@ -238,7 +240,7 @@ E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----`))
// CN=Certum Trusted Network CA 2; OU=Certum Certification Authority; O=Unizeto Technologies S.A.; C=PL
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB
gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu
QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG
@@ -274,7 +276,7 @@ DrW5viSP
-----END CERTIFICATE-----`))
// CN=Autoridad de Certificacion Firmaprofesional CIF A62634068; C=ES
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
@@ -311,7 +313,7 @@ GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
-----END CERTIFICATE-----`))
// CN=ANF Secure Server Root CA; OU=ANF CA Raiz; O=ANF Autoridad de Certificacion; C=ES; SerialNumber=G63287510
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV
BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk
YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV
@@ -347,7 +349,7 @@ tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----`))
// CN=Buypass Class 2 Root CA; O=Buypass AS-983163327; C=NO
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
@@ -380,7 +382,7 @@ Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
-----END CERTIFICATE-----`))
// CN=Buypass Class 3 Root CA; O=Buypass AS-983163327; C=NO
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
@@ -413,7 +415,7 @@ u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
-----END CERTIFICATE-----`))
// CN=Certainly Root R1; O=Certainly; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
@@ -446,7 +448,7 @@ OV+KmalBWQewLK8=
-----END CERTIFICATE-----`))
// CN=Certainly Root E1; O=Certainly; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
@@ -461,7 +463,7 @@ BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----`))
// CN=Certigna; O=Dhimyotis; C=FR
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
@@ -485,7 +487,7 @@ WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----`))
// CN=Certigna Root CA; OU=0002 48146308100036; O=Dhimyotis; C=FR
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
@@ -523,7 +525,7 @@ jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
-----END CERTIFICATE-----`))
// OU=certSIGN ROOT CA; O=certSIGN; C=RO
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
@@ -545,7 +547,7 @@ i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
-----END CERTIFICATE-----`))
// OU=certSIGN ROOT CA G2; O=CERTSIGN SA; C=RO
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
@@ -578,7 +580,7 @@ QRBdJ3NghVdJIgc=
-----END CERTIFICATE-----`))
// CN=HiPKI Root CA - G1; O=Chunghwa Telecom Co., Ltd.; C=TW
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
@@ -611,7 +613,7 @@ YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
-----END CERTIFICATE-----`))
// OU=ePKI Root Certification Authority; O=Chunghwa Telecom Co., Ltd.; C=TW
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
@@ -646,7 +648,7 @@ hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----`))
// CN=D-TRUST BR Root CA 1 2020; O=D-Trust GmbH; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
@@ -666,7 +668,7 @@ dWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----`))
// CN=D-TRUST EV Root CA 1 2020; O=D-Trust GmbH; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
@@ -686,7 +688,7 @@ gfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----`))
// CN=D-TRUST Root Class 3 CA 2 EV 2009; O=D-Trust GmbH; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
@@ -713,7 +715,7 @@ KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
-----END CERTIFICATE-----`))
// CN=D-TRUST Root Class 3 CA 2 2009; O=D-Trust GmbH; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
@@ -740,7 +742,7 @@ Johw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----`))
// CN=T-TeleSec GlobalRoot Class 3; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
@@ -765,7 +767,7 @@ TpPDpFQUWw==
-----END CERTIFICATE-----`))
// CN=T-TeleSec GlobalRoot Class 2; OU=T-Systems Trust Center; O=T-Systems Enterprise Services GmbH; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
@@ -790,7 +792,7 @@ BSeOE6Fuwg==
-----END CERTIFICATE-----`))
// CN=DigiCert TLS RSA4096 Root G5; O=DigiCert, Inc.; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
@@ -823,7 +825,7 @@ ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----`))
// CN=DigiCert TLS ECC P384 Root G5; O=DigiCert, Inc.; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
@@ -839,7 +841,7 @@ DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----`))
// CN=DigiCert Assured ID Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
@@ -863,7 +865,7 @@ H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
-----END CERTIFICATE-----`))
// CN=DigiCert Assured ID Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
@@ -887,7 +889,7 @@ IhNzbM8m9Yop5w==
-----END CERTIFICATE-----`))
// CN=DigiCert Assured ID Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
@@ -904,7 +906,7 @@ JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
-----END CERTIFICATE-----`))
// CN=DigiCert Global Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
@@ -928,7 +930,7 @@ CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----`))
// CN=DigiCert Global Root G2; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
@@ -952,7 +954,7 @@ MrY=
-----END CERTIFICATE-----`))
// CN=DigiCert Global Root G3; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
@@ -969,7 +971,7 @@ sycX
-----END CERTIFICATE-----`))
// CN=DigiCert High Assurance EV Root CA; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
@@ -994,7 +996,7 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
-----END CERTIFICATE-----`))
// CN=DigiCert Trusted Root G4; OU=www.digicert.com; O=DigiCert Inc; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
@@ -1028,7 +1030,7 @@ gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----`))
// CN=QuoVadis Root CA 2; O=QuoVadis Limited; C=BM
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
@@ -1063,7 +1065,7 @@ ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
-----END CERTIFICATE-----`))
// CN=QuoVadis Root CA 2 G3; O=QuoVadis Limited; C=BM
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
@@ -1096,7 +1098,7 @@ WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
-----END CERTIFICATE-----`))
// CN=QuoVadis Root CA 3 G3; O=QuoVadis Limited; C=BM
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
@@ -1129,7 +1131,7 @@ ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----`))
// CN=CA Disig Root R2; O=Disig a.s.; L=Bratislava; C=SK
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
@@ -1162,7 +1164,7 @@ L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----`))
// CN=emSign ECC Root CA - G3; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
@@ -1179,7 +1181,7 @@ CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
-----END CERTIFICATE-----`))
// CN=emSign Root CA - G1; OU=emSign PKI; O=eMudhra Technologies Limited; C=IN
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
@@ -1203,7 +1205,7 @@ iN66zB+Afko=
-----END CERTIFICATE-----`))
// CN=AffirmTrust Commercial; O=AffirmTrust; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
@@ -1225,7 +1227,7 @@ nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----`))
// CN=Atos TrustedRoot 2011; O=Atos; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
@@ -1248,7 +1250,7 @@ KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----`))
// CN=Atos TrustedRoot Root CA ECC TLS 2021; O=Atos; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w
LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w
CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0
@@ -1264,7 +1266,7 @@ CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo
-----END CERTIFICATE-----`))
// CN=Atos TrustedRoot Root CA RSA TLS 2021; O=Atos; C=DE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM
MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx
MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00
@@ -1297,7 +1299,7 @@ oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==
-----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign Root CA - R6; O=GlobalSign
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
@@ -1331,7 +1333,7 @@ JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
-----END CERTIFICATE-----`))
// CN=GlobalSign Root E46; O=GlobalSign nv-sa; C=BE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx
CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD
ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw
@@ -1346,7 +1348,7 @@ DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ
-----END CERTIFICATE-----`))
// CN=GlobalSign Root R46; O=GlobalSign nv-sa; C=BE
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA
MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD
VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy
@@ -1379,7 +1381,7 @@ vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6
-----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign ECC Root CA - R5; O=GlobalSign
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
@@ -1395,7 +1397,7 @@ xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign Root CA - R3; O=GlobalSign
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
@@ -1418,7 +1420,7 @@ WD9f
-----END CERTIFICATE-----`))
// CN=Starfield Root Certificate Authority - G2; O=Starfield Technologies, Inc.; L=Scottsdale; ST=Arizona; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
@@ -1443,7 +1445,7 @@ mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----`))
// CN=Go Daddy Root Certificate Authority - G2; O=GoDaddy.com, Inc.; L=Scottsdale; ST=Arizona; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
@@ -1468,7 +1470,7 @@ LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
-----END CERTIFICATE-----`))
// CN=GlobalSign; OU=GlobalSign ECC Root CA - R4; O=GlobalSign
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
@@ -1482,7 +1484,7 @@ bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----`))
// CN=GTS Root R4; O=Google Trust Services LLC; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
@@ -1497,7 +1499,7 @@ p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
-----END CERTIFICATE-----`))
// CN=GTS Root R2; O=Google Trust Services LLC; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
@@ -1530,7 +1532,7 @@ JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
-----END CERTIFICATE-----`))
// CN=GTS Root R1; O=Google Trust Services LLC; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
@@ -1563,7 +1565,7 @@ bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
-----END CERTIFICATE-----`))
// CN=GTS Root R3; O=Google Trust Services LLC; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
@@ -1578,7 +1580,7 @@ ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
-----END CERTIFICATE-----`))
// CN=ACCVRAIZ1; OU=PKIACCV; O=ACCV; C=ES
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
@@ -1624,7 +1626,7 @@ pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----`))
// OU=AC RAIZ FNMT-RCM; O=FNMT-RCM; C=ES
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx
CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ
WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ
@@ -1658,7 +1660,7 @@ uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----`))
// CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS; OU=Ceres; O=FNMT-RCM; C=ES; OrganizationIdentifier=VATES-Q2826004J
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw
CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw
FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S
@@ -1676,7 +1678,7 @@ v+c=
-----END CERTIFICATE-----`))
// CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1; OU=Kamu Sertifikasyon Merkezi - Kamu SM; O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK; L=Gebze - Kocaeli; C=TR
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx
GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp
bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w
@@ -1704,7 +1706,7 @@ lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----`))
// CN=HARICA TLS RSA Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
@@ -1739,7 +1741,7 @@ xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
-----END CERTIFICATE-----`))
// CN=HARICA TLS ECC Root CA 2021; O=Hellenic Academic and Research Institutions CA; C=GR
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
@@ -1756,7 +1758,7 @@ nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----`))
// CN=IdenTrust Commercial Root CA 1; O=IdenTrust; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
@@ -1789,7 +1791,7 @@ mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
-----END CERTIFICATE-----`))
// CN=ISRG Root X1; O=Internet Security Research Group; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
@@ -1822,7 +1824,7 @@ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----`))
// CN=ISRG Root X2; O=Internet Security Research Group; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
@@ -1838,7 +1840,7 @@ tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
-----END CERTIFICATE-----`))
// CN=Izenpe.com; O=IZENPE S.A.; C=ES
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
@@ -1874,7 +1876,7 @@ QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----`))
// CN=SZAFIR ROOT CA2; O=Krajowa Izba Rozliczeniowa S.A.; C=PL
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL
BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6
ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw
@@ -1897,7 +1899,7 @@ LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----`))
// CN=e-Szigno Root CA 2017; O=Microsec Ltd.; L=Budapest; C=HU; OrganizationIdentifier=VATHU-23584497
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
@@ -1914,7 +1916,7 @@ jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
-----END CERTIFICATE-----`))
// CN=Microsec e-Szigno Root CA 2009; O=Microsec Ltd.; L=Budapest; C=HU; EmailAddress=info@e-szigno.hu
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
@@ -1940,7 +1942,7 @@ HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
-----END CERTIFICATE-----`))
// CN=Microsoft ECC Root Certificate Authority 2017; O=Microsoft Corporation; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
@@ -1957,7 +1959,7 @@ iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----`))
// CN=Microsoft RSA Root Certificate Authority 2017; O=Microsoft Corporation; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
@@ -1992,7 +1994,7 @@ RA+GsCyRxj3qrg+E
-----END CERTIFICATE-----`))
// CN=NAVER Global Root Certification Authority; O=NAVER BUSINESS PLATFORM Corp.; C=KR
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM
BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG
T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0
@@ -2027,7 +2029,7 @@ dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul
-----END CERTIFICATE-----`))
// CN=NetLock Arany (Class Gold) Főtanúsítvány; OU=Tanúsítványkiadók (Certification Services); O=NetLock Kft.; L=Budapest; C=HU
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
@@ -2053,7 +2055,7 @@ XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----`))
// CN=OISTE WISeKey Global Root GC CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
@@ -2070,7 +2072,7 @@ Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----`))
// CN=OISTE WISeKey Global Root GB CA; OU=OISTE Foundation Endorsed; O=WISeKey; C=CH
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
@@ -2094,7 +2096,7 @@ Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----`))
// CN=Security Communication ECC RootCA1; O=SECOM Trust Systems CO.,LTD.; C=JP
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT
AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD
VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx
@@ -2110,7 +2112,7 @@ be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
-----END CERTIFICATE-----`))
// OU=Security Communication RootCA2; O=SECOM Trust Systems CO.,LTD.; C=JP
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
@@ -2133,7 +2135,7 @@ SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----`))
// CN=Entrust Root Certification Authority; OU=www.entrust.net/CPS is incorporated by reference, (c) 2006 Entrust, Inc.; O=Entrust, Inc.; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
@@ -2162,7 +2164,7 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
-----END CERTIFICATE-----`))
// CN=Sectigo Public Server Authentication Root E46; O=Sectigo Limited; C=GB
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw
CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN
@@ -2178,7 +2180,7 @@ qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q
-----END CERTIFICATE-----`))
// CN=COMODO ECC Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
@@ -2196,7 +2198,7 @@ GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----`))
// CN=COMODO Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIID0DCCArigAwIBAgIQIKTEf93f4cdTYwcTiHdgEjANBgkqhkiG9w0BAQUFADCB
gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
@@ -2221,7 +2223,7 @@ R1uUq27UlTMdphVx8fiUylQ5PsE=
-----END CERTIFICATE-----`))
// CN=COMODO RSA Certification Authority; O=COMODO CA Limited; L=Salford; ST=Greater Manchester; C=GB
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
@@ -2257,7 +2259,7 @@ NVOFBkpdn627G190
-----END CERTIFICATE-----`))
// CN=USERTrust RSA Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
@@ -2293,7 +2295,7 @@ jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----`))
// CN=USERTrust ECC Certification Authority; O=The USERTRUST Network; L=Jersey City; ST=New Jersey; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
@@ -2311,7 +2313,7 @@ RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----`))
// CN=Sectigo Public Server Authentication Root R46; O=Sectigo Limited; C=GB
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
@@ -2345,7 +2347,7 @@ QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
-----END CERTIFICATE-----`))
// CN=Entrust Root Certification Authority - G2; OU=See www.entrust.net/legal-terms, (c) 2009 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
@@ -2372,7 +2374,7 @@ VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----`))
// CN=Entrust Root Certification Authority - EC1; OU=See www.entrust.net/legal-terms, (c) 2012 Entrust, Inc. - for authorized use only; O=Entrust, Inc.; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
@@ -2392,7 +2394,7 @@ hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----`))
// CN=SSL.com Root Certification Authority RSA; O=SSL Corporation; L=Houston; ST=Texas; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE
BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK
DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp
@@ -2428,7 +2430,7 @@ Ic2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----`))
// CN=SSL.com TLS ECC Root CA 2022; O=SSL Corporation; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw
CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT
U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2
@@ -2444,7 +2446,7 @@ b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==
-----END CERTIFICATE-----`))
// CN=SSL.com TLS RSA Root CA 2022; O=SSL Corporation; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO
MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD
DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX
@@ -2478,7 +2480,7 @@ Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU
-----END CERTIFICATE-----`))
// CN=SSL.com Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
@@ -2496,7 +2498,7 @@ gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----`))
// CN=SSL.com EV Root Certification Authority RSA R2; O=SSL Corporation; L=Houston; ST=Texas; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy
@@ -2532,7 +2534,7 @@ mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----`))
// CN=SSL.com EV Root Certification Authority ECC; O=SSL Corporation; L=Houston; ST=Texas; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp
@@ -2550,7 +2552,7 @@ h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----`))
// CN=SwissSign Gold CA - G2; O=SwissSign AG; C=CH
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
@@ -2585,7 +2587,7 @@ Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----`))
// CN=TWCA CYBER Root CA; OU=Root CA; O=TAIWAN-CA; C=TW
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ
MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290
IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5
@@ -2619,7 +2621,7 @@ t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
-----END CERTIFICATE-----`))
// CN=TWCA Global Root CA; OU=Root CA; O=TAIWAN-CA; C=TW
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
@@ -2652,7 +2654,7 @@ KwbQBM0=
-----END CERTIFICATE-----`))
// CN=TeliaSonera Root CA v1; O=TeliaSonera
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
@@ -2684,7 +2686,7 @@ SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----`))
// CN=Telia Root CA v2; O=Telia Finland Oyj; C=FI
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
@@ -2718,7 +2720,7 @@ rBPuUBQemMc=
-----END CERTIFICATE-----`))
// CN=Trustwave Global ECC P384 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
@@ -2737,7 +2739,7 @@ Sw==
-----END CERTIFICATE-----`))
// CN=Trustwave Global ECC P256 Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
@@ -2754,7 +2756,7 @@ DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----`))
// CN=SecureTrust CA; O=SecureTrust Corporation; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
@@ -2778,7 +2780,7 @@ CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
-----END CERTIFICATE-----`))
// CN=Trustwave Global Certification Authority; O=Trustwave Holdings, Inc.; L=Chicago; ST=Illinois; C=US
pool.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
chromeIncluded.AppendCertsFromPEM([]byte(`-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw
CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x
ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1
@@ -2812,5 +2814,4 @@ h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9
EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK
yeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----`))
return pool
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,6 @@ var _ adapter.CertificateStore = (*Store)(nil)
type Store struct {
access sync.RWMutex
storeType string
systemPool *x509.CertPool
currentPool *x509.CertPool
certificate string
@@ -32,13 +31,9 @@ type Store struct {
}
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
storeType := options.Store
if storeType == "" {
storeType = C.CertificateStoreSystem
}
var systemPool *x509.CertPool
switch storeType {
case C.CertificateStoreSystem:
switch options.Store {
case C.CertificateStoreSystem, "":
systemPool = x509.NewCertPool()
platformInterface := service.FromContext[adapter.PlatformInterface](ctx)
var systemValid bool
@@ -56,13 +51,16 @@ func NewStore(ctx context.Context, logger logger.Logger, options option.Certific
}
systemPool = certPool
}
case C.CertificateStoreMozilla, C.CertificateStoreChrome:
case C.CertificateStoreMozilla:
systemPool = mozillaIncluded
case C.CertificateStoreChrome:
systemPool = chromeIncluded
case C.CertificateStoreNone:
systemPool = nil
default:
return nil, E.New("unknown certificate store: ", options.Store)
}
store := &Store{
storeType: storeType,
systemPool: systemPool,
certificate: strings.Join(options.Certificate, "\n"),
certificatePaths: options.CertificatePath,
@@ -126,9 +124,13 @@ func (s *Store) Pool() *x509.CertPool {
}
func (s *Store) update() error {
currentPool, err := s.newBasePool()
if err != nil {
return err
s.access.Lock()
defer s.access.Unlock()
var currentPool *x509.CertPool
if s.systemPool == nil {
currentPool = x509.NewCertPool()
} else {
currentPool = s.systemPool.Clone()
}
if s.certificate != "" {
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
@@ -163,30 +165,10 @@ func (s *Store) update() error {
if firstErr != nil {
return firstErr
}
s.access.Lock()
defer s.access.Unlock()
s.currentPool = currentPool
return nil
}
func (s *Store) newBasePool() (*x509.CertPool, error) {
switch s.storeType {
case C.CertificateStoreSystem:
if s.systemPool == nil {
return x509.NewCertPool(), nil
}
return s.systemPool.Clone(), nil
case C.CertificateStoreMozilla:
return newMozillaIncluded(), nil
case C.CertificateStoreChrome:
return newChromeIncluded(), nil
case C.CertificateStoreNone:
return x509.NewCertPool(), nil
default:
return nil, E.New("unknown certificate store: ", s.storeType)
}
}
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
files, err := os.ReadDir(dir)
if err != nil {

View File

@@ -1,12 +1,15 @@
package cloudflare
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/tidwall/gjson"
)
type CloudflareApi struct {
@@ -22,93 +25,50 @@ func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi {
}
func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) {
serial, err := GenerateRandomAndroidSerial()
if err != nil {
return nil, fmt.Errorf("failed to generate serial: %v", err)
}
data := Registration{
Key: publicKey,
InstallID: "",
FcmToken: "",
Tos: TimeAsCfString(time.Now()),
Model: "PC",
Serial: serial,
OsVersion: "",
KeyType: KeyTypeWg,
TunType: TunTypeWg,
Locale: "en-US",
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %v", err)
}
request, err := http.NewRequest("POST", ApiUrl+"/"+ApiVersion+"/reg", bytes.NewBuffer(jsonData))
request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader(
fmt.Sprintf(
"{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}",
time.Now().Format("2006-01-02T15:04:05.000Z"),
publicKey,
),
))
if err != nil {
return nil, err
}
for k, v := range Headers {
request.Header.Set(k, v)
}
response, err := api.client.Do(request.WithContext(ctx))
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to register: %v", response.StatusCode)
if response.StatusCode != 200 {
return nil, fmt.Errorf("status code is not 200")
}
profile := new(CloudflareProfile)
return profile, json.NewDecoder(response.Body).Decode(profile)
}
func (api *CloudflareApi) EnrollKey(ctx context.Context, authToken string, id string, keyType, tunType, publicKey string) (*CloudflareProfile, error) {
deviceUpdate := DeviceUpdate{
Name: "PC",
Key: publicKey,
KeyType: keyType,
TunType: tunType,
}
jsonData, err := json.Marshal(deviceUpdate)
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %v", err)
}
request, err := http.NewRequest("PATCH", ApiUrl+"/"+ApiVersion+"/reg/"+id, bytes.NewBuffer(jsonData))
content, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
for k, v := range Headers {
request.Header.Set(k, v)
}
request.Header.Set("Authorization", "Bearer "+authToken)
response, err := api.client.Do(request.WithContext(ctx))
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to enroll key: %v", response.StatusCode)
}
profile := new(CloudflareProfile)
return profile, json.NewDecoder(response.Body).Decode(profile)
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
}
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) {
request, err := http.NewRequest("GET", ApiUrl+"/"+ApiVersion+"/reg/"+id, nil)
request, err := http.NewRequest("GET", "https://api.cloudflareclient.com/v0i1909051800/reg/"+id, nil)
if err != nil {
return nil, err
}
for k, v := range Headers {
request.Header.Set(k, v)
}
request.Header.Set("Authorization", "Bearer "+authToken)
response, err := api.client.Do(request.WithContext(ctx))
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get profile: %v", response.StatusCode)
if response.StatusCode != 200 {
return nil, fmt.Errorf("status code is not 200")
}
content, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
profile := new(CloudflareProfile)
return profile, json.NewDecoder(response.Body).Decode(profile)
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
}

View File

@@ -1,25 +0,0 @@
package cloudflare
const (
ApiUrl = "https://api.cloudflareclient.com"
ApiVersion = "v0a4471"
ConnectSNI = "consumer-masque.cloudflareclient.com"
// unused for now
ZeroTierSNI = "zt-masque.cloudflareclient.com"
ConnectURI = "https://cloudflareaccess.com"
DefaultModel = "PC"
KeyTypeWg = "curve25519"
TunTypeWg = "wireguard"
KeyTypeMasque = "secp256r1"
TunTypeMasque = "masque"
DefaultLocale = "en_US"
DefaultEndpointH2V4 = "162.159.198.2"
DefaultEndpointH2V6 = ""
)
var Headers = map[string]string{
"User-Agent": "WARP for Android",
"CF-Client-Version": "a-6.35-4471",
"Content-Type": "application/json; charset=UTF-8",
"Connection": "Keep-Alive",
}

View File

@@ -1,132 +0,0 @@
package cloudflare
import (
"strings"
)
type Registration struct {
Key string `json:"key"`
InstallID string `json:"install_id"`
FcmToken string `json:"fcm_token"`
Tos string `json:"tos"`
Model string `json:"model"`
Serial string `json:"serial_number"`
OsVersion string `json:"os_version"`
KeyType string `json:"key_type"`
TunType string `json:"tunnel_type"`
Locale string `json:"locale"`
}
type CloudflareProfile struct {
ID string `json:"id"`
Type string `json:"type"`
Model string `json:"model"`
Name string `json:"name"`
Key string `json:"key"`
KeyType string `json:"key_type"`
TunType string `json:"tunnel_type"`
Account Account `json:"account"`
Config Config `json:"config"`
// WarpEnabled not set for ZeroTier
WarpEnabled bool `json:"warp_enabled,omitempty"`
// Waitlist not set for ZeroTier
Waitlist bool `json:"waitlist_enabled,omitempty"`
Created string `json:"created"`
Updated string `json:"updated"`
// Tos not set for ZeroTier
Tos string `json:"tos,omitempty"`
// Place not set for ZeroTier
Place int `json:"place,omitempty"`
Locale string `json:"locale"`
// Enabled not set for ZeroTier
Enabled bool `json:"enabled,omitempty"`
InstallID string `json:"install_id"`
// Token only set for /reg call
Token string `json:"token,omitempty"`
FcmToken string `json:"fcm_token"`
// SerialNumber not set for ZeroTier
SerialNumber string `json:"serial_number,omitempty"`
Policy Policy `json:"policy"`
}
type Account struct {
ID string `json:"id"`
AccountType string `json:"account_type"`
// Created not set for ZeroTier
Created string `json:"created,omitempty"`
// Updated not set for ZeroTier
Updated string `json:"updated,omitempty"`
// Managed only set for ZeroTier
Managed string `json:"managed,omitempty"`
// Organization only set for ZeroTier
Organization string `json:"organization,omitempty"`
// PremiumData not set for ZeroTier
PremiumData int `json:"premium_data,omitempty"`
// Quota not set for ZeroTier
Quota int `json:"quota,omitempty"`
// WarpPlus not set for ZeroTier
WarpPlus bool `json:"warp_plus,omitempty"`
// ReferralCode not set for ZeroTier
ReferralCount int `json:"referral_count,omitempty"`
// ReferralRenewalCount not set for ZeroTier
ReferralRenewalCount int `json:"referral_renewal_countdown,omitempty"`
// Role not set for ZeroTier
Role string `json:"role,omitempty"`
// License not set for ZeroTier
License string `json:"license,omitempty"`
}
type Config struct {
ClientID string `json:"client_id"`
Peers []Peer `json:"peers"`
Interface struct {
Addresses struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
} `json:"addresses"`
} `json:"interface"`
Services struct {
HTTPProxy string `json:"http_proxy"`
} `json:"services"`
}
type Peer struct {
PublicKey string `json:"public_key"`
Endpoint struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
Host string `json:"host"`
Ports []int `json:"ports"`
} `json:"endpoint"`
}
type Policy struct {
TunnelProtocol string `json:"tunnel_protocol"`
}
type DeviceUpdate struct {
Key string `json:"key"`
KeyType string `json:"key_type"`
TunType string `json:"tunnel_type"`
Name string `json:"name,omitempty"`
}
type APIError struct {
Result interface{} `json:"result"`
Success bool `json:"success"`
Errors []ErrorInfo `json:"errors"`
Messages []string `json:"messages"`
}
type ErrorInfo struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (e *APIError) Error() string {
errors := make([]string, len(e.Errors))
for i, err := range e.Errors {
errors[i] = err.Message
}
return strings.Join(errors, ",")
}

View File

@@ -4,14 +4,12 @@ import (
"context"
"net"
"net/http"
"time"
)
type CloudflareApiOption func(api *CloudflareApi)
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption {
return func(api *CloudflareApi) {
api.client.Timeout = 30 * time.Second
api.client.Transport = &http.Transport{
DialContext: dialContext,
}

View File

@@ -0,0 +1,64 @@
package cloudflare
import "time"
type CloudflareProfile struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Key string `json:"key"`
Account struct {
ID string `json:"id"`
AccountType string `json:"account_type"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
PremiumData int `json:"premium_data"`
Quota int `json:"quota"`
Usage int `json:"usage"`
WARPPlus bool `json:"warp_plus"`
ReferralCount int `json:"referral_count"`
ReferralRenewalCountdown int `json:"referral_renewal_countdown"`
Role string `json:"role"`
License string `json:"license"`
TTL time.Time `json:"ttl"`
} `json:"account"`
Config struct {
ClientID string `json:"client_id"`
Interface struct {
Addresses struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
} `json:"addresses"`
} `json:"interface"`
Peers []struct {
PublicKey string `json:"public_key"`
Endpoint struct {
V4 string `json:"v4"`
V6 string `json:"v6"`
Host string `json:"host"`
Ports []int `json:"ports"`
} `json:"endpoint"`
} `json:"peers"`
Services struct {
HTTPProxy string `json:"http_proxy"`
} `json:"services"`
Metrics struct {
Ping int `json:"ping"`
Report int `json:"report"`
} `json:"metrics"`
} `json:"config"`
Token string `json:"token"`
WARPEnabled bool `json:"warp_enabled"`
WaitlistEnabled bool `json:"waitlist_enabled"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Tos time.Time `json:"tos"`
Place int `json:"place"`
Locale string `json:"locale"`
Enabled bool `json:"enabled"`
InstallID string `json:"install_id"`
FcmToken string `json:"fcm_token"`
Policy struct {
TunnelProtocol string `json:"tunnel_protocol"`
} `json:"policy"`
}

View File

@@ -1,19 +0,0 @@
package cloudflare
import (
"crypto/rand"
"encoding/hex"
"time"
)
func GenerateRandomAndroidSerial() (string, error) {
serial := make([]byte, 8)
if _, err := rand.Read(serial); err != nil {
return "", err
}
return hex.EncodeToString(serial), nil
}
func TimeAsCfString(t time.Time) string {
return t.Format("2006-01-02T15:04:05.000-07:00")
}

View File

@@ -1,77 +0,0 @@
package congestion
import (
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/congestion"
"github.com/sagernet/sing-quic/congestion_bbr1"
"github.com/sagernet/sing-quic/congestion_bbr2"
congestion_meta1 "github.com/sagernet/sing-quic/congestion_meta1"
congestion_meta2 "github.com/sagernet/sing-quic/congestion_meta2"
E "github.com/sagernet/sing/common/exceptions"
)
func NewCongestionControl(name string, cwnd int, timeFunc func() time.Time) (func(conn *quic.Conn) congestion.CongestionControl, error) {
if timeFunc == nil {
timeFunc = time.Now
}
if cwnd == 0 {
cwnd = 32
}
switch name {
case "", "bbr":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_meta2.NewBbrSender(
congestion_meta2.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
congestion.ByteCount(cwnd)*congestion.ByteCount(conn.Config().InitialPacketSize),
)
}, nil
case "bbr_standard":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_bbr1.NewBbrSender(
congestion_bbr1.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
congestion_bbr1.InitialCongestionWindowPackets,
congestion_bbr1.MaxCongestionWindowPackets,
)
}, nil
case "bbr2":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_bbr2.NewBBR2Sender(
congestion_bbr2.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
0,
false,
)
}, nil
case "bbr2_variant":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_bbr2.NewBBR2Sender(
congestion_bbr2.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
32*congestion.ByteCount(conn.Config().InitialPacketSize),
true,
)
}, nil
case "cubic":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_meta1.NewCubicSender(
congestion_meta1.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
false,
)
}, nil
case "reno":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_meta1.NewCubicSender(
congestion_meta1.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
true,
)
}, nil
default:
return nil, E.New("unknown congestion control: ", name)
}
}

View File

@@ -63,7 +63,9 @@ parseLine:
}
continue
}
ruleLine = strings.TrimSuffix(ruleLine, "|")
if strings.HasSuffix(ruleLine, "|") {
ruleLine = ruleLine[:len(ruleLine)-1]
}
var (
isExclude bool
isSuffix bool
@@ -74,7 +76,7 @@ parseLine:
)
if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
params := common.SubstringAfter(ruleLine, "$")
for param := range strings.SplitSeq(params, ",") {
for _, param := range strings.Split(params, ",") {
paramParts := strings.Split(param, "=")
var ignored bool
if len(paramParts) > 0 && len(paramParts) <= 2 {
@@ -104,7 +106,9 @@ parseLine:
ruleLine = ruleLine[2:]
isExclude = true
}
ruleLine = strings.TrimSuffix(ruleLine, "|")
if strings.HasSuffix(ruleLine, "|") {
ruleLine = ruleLine[:len(ruleLine)-1]
}
if strings.HasPrefix(ruleLine, "||") {
ruleLine = ruleLine[2:]
isSuffix = true
@@ -410,18 +414,18 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
}
func parseAdGuardHostLine(ruleLine string) (string, error) {
before, after, ok := strings.Cut(ruleLine, " ")
if !ok {
idx := strings.Index(ruleLine, " ")
if idx == -1 {
return "", os.ErrInvalid
}
address, err := netip.ParseAddr(before)
address, err := netip.ParseAddr(ruleLine[:idx])
if err != nil {
return "", err
}
if !address.IsUnspecified() {
return "", nil
}
domain := after
domain := ruleLine[idx+1:]
if !M.IsDomainName(domain) {
return "", E.New("invalid domain name: ", domain)
}

View File

@@ -70,20 +70,9 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
}
if platformInterface != nil && platformInterface.UsePlatformAutoDetectInterfaceControl() {
interfaceName := options.BindInterface
bindFunc := func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return platformInterface.BindInterfaceControl(int(fd), interfaceName)
})
}
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
if options.RoutingMark > 0 {
if !C.IsLinux {
@@ -160,10 +149,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
} 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
@@ -253,7 +239,7 @@ 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 {
@@ -343,9 +329,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
}
}

View File

@@ -136,16 +136,18 @@ func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, d
go startRacer(fallbackCtx, false, iif)
}
var errors []error
for res := range results {
if res.error == nil {
return res.Conn, res.primary, nil
}
errors = append(errors, res.error)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, res.primary, nil
}
errors = append(errors, res.error)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
}
}
}
return nil, false, E.Errors(errors...)
}
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
@@ -182,12 +184,6 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces()
myInterfaces := networkManager.InterfaceMonitor().MyInterfaces()
if len(myInterfaces) > 0 {
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Contains(myInterfaces, it.Name)
})
}
switch strategy {
case C.NetworkStrategyDefault:
if len(interfaceType) == 0 {

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

@@ -19,6 +19,11 @@ func oldWriteString(writer varbin.Writer, value string) error {
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)
@@ -219,7 +224,7 @@ func TestGeositeWriteReadCompat(t *testing.T) {
func generateLargeItems(count int) map[string][]Item {
items := make([]Item, count)
for i := range count {
for i := 0; i < count; i++ {
items[i] = Item{
Type: ItemType(i % 4),
Value: strings.Repeat("x", i%200) + ".com",

View File

@@ -48,6 +48,12 @@ func NewReader(readSeeker io.ReadSeeker) (*Reader, []string, error) {
return reader, codes, nil
}
type geositeMetadata struct {
Code string
Index uint64
Length uint64
}
func (r *Reader) readMetadata() error {
counter := &readCounter{Reader: r.reader}
reader := bufio.NewReader(counter)
@@ -95,9 +101,6 @@ func (r *Reader) readMetadata() error {
}
func (r *Reader) Read(code string) ([]Item, error) {
r.access.Lock()
defer r.access.Unlock()
index, exists := r.domainIndex[code]
if !exists {
return nil, E.New("code ", code, " not exists!")

View File

@@ -11,13 +11,3 @@ func ContextWithIsExternalConnection(ctx context.Context) context.Context {
func IsExternalConnectionFromContext(ctx context.Context) bool {
return ctx.Value(contextKeyIsExternalConnection{}) != nil
}
type contextKeyIsProviderConnection struct{}
func ContextWithIsProviderConnection(ctx context.Context) context.Context {
return context.WithValue(ctx, contextKeyIsProviderConnection{}, true)
}
func IsProviderConnectionFromContext(ctx context.Context) bool {
return ctx.Value(contextKeyIsProviderConnection{}) != nil
}

View File

@@ -17,31 +17,30 @@ type Group struct {
type groupConnItem struct {
conn io.Closer
isExternal bool
isProvider bool
}
func NewGroup() *Group {
return &Group{}
}
func (g *Group) NewConn(conn net.Conn, isExternal bool, isProvider bool) net.Conn {
func (g *Group) NewConn(conn net.Conn, isExternal bool) net.Conn {
g.access.Lock()
defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal, isProvider})
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
return &Conn{Conn: conn, group: g, element: item}
}
func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool, isProvider bool) net.PacketConn {
func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketConn {
g.access.Lock()
defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal, isProvider})
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
return &PacketConn{PacketConn: conn, group: g, element: item}
}
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool, isProvider bool) N.PacketConn {
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn {
g.access.Lock()
defer g.access.Unlock()
item := g.connections.PushBack(&groupConnItem{conn, isExternal, isProvider})
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
return &SingPacketConn{PacketConn: conn, group: g, element: item}
}

View File

@@ -131,7 +131,7 @@ func (j *ClientHello) parseHandshake(hs []byte) error {
return &ParseError{LengthErr, 7}
}
for i := range numCiphers {
for i := 0; i < numCiphers; i++ {
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
cipherSuites = append(cipherSuites, cipherSuite)
}
@@ -234,7 +234,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 16}
}
for i := range numCurves {
for i := 0; i < numCurves; i++ {
ecType := uint16(sex[i*2])<<8 | uint16(sex[1+i*2])
ellipticCurves = append(ellipticCurves, ecType)
}
@@ -256,7 +256,7 @@ func (j *ClientHello) parseExtensions(exs []byte) error {
return &ParseError{LengthErr, 18}
}
for i := range numPF {
for i := 0; i < numPF; i++ {
ellipticCurvePF[i] = uint8(sex[i])
}
case versionExtensionType:

View File

@@ -12,6 +12,7 @@ type klock struct {
ref uint64
}
// Create new Kmutex
func New[T comparable]() *Kmutex[T] {
l := sync.Mutex{}
return &Kmutex[T]{
@@ -20,6 +21,7 @@ func New[T comparable]() *Kmutex[T] {
}
}
// Unlock Kmutex by unique ID
func (km *Kmutex[T]) Unlock(key T) {
km.l.Lock()
defer km.l.Unlock()
@@ -35,6 +37,7 @@ func (km *Kmutex[T]) Unlock(key T) {
kl.cond.Signal()
}
// Lock Kmutex by unique ID
func (km *Kmutex[T]) Lock(key T) {
km.l.Lock()
defer km.l.Unlock()

View File

@@ -6,7 +6,48 @@
package ktls
import "golang.org/x/crypto/cryptobyte"
import (
"fmt"
"golang.org/x/crypto/cryptobyte"
)
// The marshalingFunction type is an adapter to allow the use of ordinary
// functions as cryptobyte.MarshalingValue.
type marshalingFunction func(b *cryptobyte.Builder) error
func (f marshalingFunction) Marshal(b *cryptobyte.Builder) error {
return f(b)
}
// addBytesWithLength appends a sequence of bytes to the cryptobyte.Builder. If
// the length of the sequence is not the value specified, it produces an error.
func addBytesWithLength(b *cryptobyte.Builder, v []byte, n int) {
b.AddValue(marshalingFunction(func(b *cryptobyte.Builder) error {
if len(v) != n {
return fmt.Errorf("invalid value length: expected %d, got %d", n, len(v))
}
b.AddBytes(v)
return nil
}))
}
// addUint64 appends a big-endian, 64-bit value to the cryptobyte.Builder.
func addUint64(b *cryptobyte.Builder, v uint64) {
b.AddUint32(uint32(v >> 32))
b.AddUint32(uint32(v))
}
// readUint64 decodes a big-endian, 64-bit value into out and advances over it.
// It reports whether the read was successful.
func readUint64(s *cryptobyte.String, out *uint64) bool {
var hi, lo uint32
if !s.ReadUint32(&hi) || !s.ReadUint32(&lo) {
return false
}
*out = uint64(hi)<<32 | uint64(lo)
return true
}
// readUint8LengthPrefixed acts like s.ReadUint8LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String.
@@ -20,6 +61,12 @@ func readUint16LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint16LengthPrefixed((*cryptobyte.String)(out))
}
// readUint24LengthPrefixed acts like s.ReadUint24LengthPrefixed, but targets a
// []byte instead of a cryptobyte.String.
func readUint24LengthPrefixed(s *cryptobyte.String, out *[]byte) bool {
return s.ReadUint24LengthPrefixed((*cryptobyte.String)(out))
}
type keyUpdateMsg struct {
updateRequested bool
}
@@ -78,6 +125,11 @@ const (
typeMessageHash uint8 = 254 // synthetic message
)
// TLS compression types.
const (
compressionNone uint8 = 0
)
// TLS extension numbers
const (
extensionServerName uint16 = 0

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

@@ -77,5 +77,78 @@ func (c *Conn) writeRecordLocked(typ uint16, data []byte) (n int, err error) {
if !c.kernelTx {
return c.rawConn.WriteRecordLocked(typ, data)
}
/*for len(data) > 0 {
m := len(data)
if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload {
m = maxPayload
}
_, err = c.writeKernelRecord(typ, data[:m])
if err != nil {
return
}
n += m
data = data[m:]
}*/
return c.writeKernelRecord(typ, data)
}
const (
// tcpMSSEstimate is a conservative estimate of the TCP maximum segment
// size (MSS). A constant is used, rather than querying the kernel for
// the actual MSS, to avoid complexity. The value here is the IPv6
// minimum MTU (1280 bytes) minus the overhead of an IPv6 header (40
// bytes) and a TCP header with timestamps (32 bytes).
tcpMSSEstimate = 1208
// recordSizeBoostThreshold is the number of bytes of application data
// sent after which the TLS record size will be increased to the
// maximum.
recordSizeBoostThreshold = 128 * 1024
)
func (c *Conn) maxPayloadSizeForWrite(typ uint16) int {
if /*c.config.DynamicRecordSizingDisabled ||*/ typ != recordTypeApplicationData {
return maxPlaintext
}
if *c.rawConn.PacketsSent >= recordSizeBoostThreshold {
return maxPlaintext
}
// Subtract TLS overheads to get the maximum payload size.
payloadBytes := tcpMSSEstimate - recordHeaderLen - c.rawConn.Out.ExplicitNonceLen()
if rawCipher := *c.rawConn.Out.Cipher; rawCipher != nil {
switch ciph := rawCipher.(type) {
case cipher.Stream:
payloadBytes -= (*c.rawConn.Out.Mac).Size()
case cipher.AEAD:
payloadBytes -= ciph.Overhead()
/*case cbcMode:
blockSize := ciph.BlockSize()
// The payload must fit in a multiple of blockSize, with
// room for at least one padding byte.
payloadBytes = (payloadBytes & ^(blockSize - 1)) - 1
// The RawMac is appended before padding so affects the
// payload size directly.
payloadBytes -= c.out.mac.Size()*/
default:
panic("unknown cipher type")
}
}
if *c.rawConn.Vers == tls.VersionTLS13 {
payloadBytes-- // encrypted ContentType
}
// Allow packet growth in arithmetic progression up to max.
pkt := *c.rawConn.PacketsSent
*c.rawConn.PacketsSent++
if pkt > 1000 {
return maxPlaintext // avoid overflow in multiply below
}
n := payloadBytes * int(pkt+1)
if n > maxPlaintext {
n = maxPlaintext
}
return n
}

View File

@@ -1,164 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package list
// Element is an element of a linked list.
type Element[T any] struct {
next, prev *Element[T]
list *List[T]
Value T
}
func (e *Element[T]) Next() *Element[T] {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
func (e *Element[T]) Prev() *Element[T] {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
func (e *Element[T]) Remove() bool {
if e.list == nil {
return false
}
e.list.remove(e)
return true
}
type List[T any] struct {
root Element[T]
len int
}
func (l *List[T]) Init() *List[T] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
func New[T any]() *List[T] { return new(List[T]).Init() }
func (l *List[T]) Len() int { return l.len }
func (l *List[T]) Front() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.next
}
func (l *List[T]) Back() *Element[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}
func (l *List[T]) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] {
return l.insert(&Element[T]{Value: v}, at)
}
func (l *List[T]) remove(e *Element[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil
e.prev = nil
e.list = nil
l.len--
}
func (l *List[T]) Remove(e *Element[T]) T {
if e.list == l {
l.remove(e)
}
return e.Value
}
func (l *List[T]) PushFront(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, &l.root)
}
func (l *List[T]) PushBack(v T) *Element[T] {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
return l.insertValue(v, mark.prev)
}
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
if mark.list != l {
return nil
}
return l.insertValue(v, mark)
}
func (l *List[T]) MoveToFront(e *Element[T]) {
if e.list != l || l.root.next == e {
return
}
l.move(e, &l.root)
}
func (l *List[T]) MoveToBack(e *Element[T]) {
if e.list != l || l.root.prev == e {
return
}
l.move(e, l.root.prev)
}
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
func (l *List[T]) move(e, at *Element[T]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}

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
@@ -77,7 +74,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if err != nil {
return nil, err
}
l.logger.Notice("tcp server started at ", tcpListener.Addr())
l.logger.Info("tcp server started at ", tcpListener.Addr())
l.tcpListener = tcpListener
return tcpListener, err
}

View File

@@ -54,7 +54,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
}
l.udpConn = udpConn.(*net.UDPConn)
l.udpAddr = bindAddr
l.logger.Notice("udp server started at ", udpConn.LocalAddr())
l.logger.Info("udp server started at ", udpConn.LocalAddr())
return udpConn, err
}

View File

@@ -1,38 +0,0 @@
package onclose
import (
"net"
"sync"
)
type CloseHandlerFunc = func()
type Conn struct {
net.Conn
onClose func()
once sync.Once
}
func NewConn(conn net.Conn, onClose func()) *Conn {
return &Conn{Conn: conn, onClose: onClose}
}
func (c *Conn) Close() error {
c.once.Do(c.onClose)
return c.Conn.Close()
}
type PacketConn struct {
net.PacketConn
onClose func()
once sync.Once
}
func NewPacketConn(conn net.PacketConn, onClose func()) *PacketConn {
return &PacketConn{PacketConn: conn, onClose: onClose}
}
func (c *PacketConn) Close() error {
c.once.Do(c.onClose)
return c.PacketConn.Close()
}

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,270 +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 := range 2 {
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

@@ -1,70 +1,45 @@
//go:build linux
//nolint:unused
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
@@ -73,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 range 2 {
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

@@ -109,7 +109,7 @@ func getInterfaceDisplayName(name string) (string, error) {
if err != nil {
return "", err
}
for deviceSpan := range strings.SplitSeq(string(content), "Ethernet Address") {
for _, deviceSpan := range strings.Split(string(content), "Ethernet Address") {
if strings.Contains(deviceSpan, "Device: "+name) {
substr := "Hardware Port: "
deviceSpan = deviceSpan[strings.Index(deviceSpan, substr)+len(substr):]

View File

@@ -40,14 +40,14 @@ func (m *connmanMonitor) ReadWIFIState() adapter.WIFIState {
defer cancel()
cmObj := m.conn.Object("net.connman", "/")
var services []any
var services []interface{}
err := cmObj.CallWithContext(ctx, "net.connman.Manager.GetServices", 0).Store(&services)
if err != nil {
return adapter.WIFIState{}
}
for _, service := range services {
servicePair, ok := service.([]any)
servicePair, ok := service.([]interface{})
if !ok || len(servicePair) != 2 {
continue
}

View File

@@ -1,4 +1,3 @@
//nolint:unused
package settings
import (
@@ -74,13 +73,13 @@ func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
scanner := bufio.NewScanner(strings.NewReader(status))
for scanner.Scan() {
line := scanner.Text()
if after, ok := strings.CutPrefix(line, "wpa_state="); ok {
state := after
if strings.HasPrefix(line, "wpa_state=") {
state := strings.TrimPrefix(line, "wpa_state=")
connected = state == "COMPLETED"
} else if after, ok := strings.CutPrefix(line, "ssid="); ok {
ssid = after
} else if after, ok := strings.CutPrefix(line, "bssid="); ok {
bssid = after
} else if strings.HasPrefix(line, "ssid=") {
ssid = strings.TrimPrefix(line, "ssid=")
} else if strings.HasPrefix(line, "bssid=") {
bssid = strings.TrimPrefix(line, "bssid=")
}
}

View File

@@ -1,6 +1,5 @@
//go:build !linux && !windows
//nolint:unused
package settings
import (

View File

@@ -54,8 +54,9 @@ type xorNonceAEAD struct {
aead cipher.AEAD
}
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
for i, b := range nonce {

View File

@@ -1,8 +1,6 @@
package sniff
import (
"slices"
"github.com/sagernet/sing-box/common/ja3"
)
@@ -17,8 +15,15 @@ const (
// Note: uQUIC with Chromium mimicry cannot be reliably distinguished from real Chromium
// since it uses the same TLS fingerprint, so it will be identified as Chromium.
func isQUICGo(fingerprint *ja3.ClientHello) bool {
if slices.Contains(fingerprint.EllipticCurves, x25519Kyber768Draft00) {
return true
for _, curve := range fingerprint.EllipticCurves {
if curve == x25519Kyber768Draft00 {
return true
}
}
return slices.Contains(fingerprint.Extensions, extensionRenegotiationInfo)
for _, ext := range fingerprint.Extensions {
if ext == extensionRenegotiationInfo {
return true
}
}
return false
}

View File

@@ -30,7 +30,7 @@ func TestSniffQUICQuicGoFingerprint(t *testing.T) {
go func() {
var packets [][]byte
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
for range 10 {
for i := 0; i < 10; i++ {
buf := make([]byte, 2048)
n, _, err := udpConn.ReadFromUDP(buf)
if err != nil {
@@ -104,7 +104,7 @@ func TestSniffQUICInitialFromQuicGo(t *testing.T) {
go func() {
var packets [][]byte
udpConn.SetReadDeadline(time.Now().Add(3 * time.Second))
for range 5 { // Capture up to 5 packets
for i := 0; i < 5; i++ { // Capture up to 5 packets
buf := make([]byte, 2048)
n, _, err := udpConn.ReadFromUDP(buf)
if err != nil {

View File

@@ -78,7 +78,7 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
}
ruleSetCompat.Version = version
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
for i := range length {
for i := uint64(0); i < length; i++ {
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
if err != nil {
err = E.Cause(err, "read rule[", i, "]")
@@ -644,7 +644,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
return
}
logicalRule.Rules = make([]option.HeadlessRule, length)
for i := range length {
for i := uint64(0); i < length; i++ {
logicalRule.Rules[i], err = readRule(reader, recovery)
if err != nil {
err = E.Cause(err, "read logical rule [", i, "]")

View File

@@ -450,7 +450,7 @@ func buildIPSet(cidrs ...string) *netipx.IPSet {
func buildLargeIPSet(count int) *netipx.IPSet {
var builder netipx.IPSetBuilder
for i := range count {
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)
}

View File

@@ -1,74 +0,0 @@
package tls
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"time"
"github.com/sagernet/quic-go/http3"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
func NewMASQUEClient(ctx context.Context, logger logger.ContextLogger, serverName string, cert [][]byte, privateKey *ecdsa.PrivateKey, peerPublicKey *ecdsa.PublicKey, options option.MASQUEOutboundTLSOptions) (Config, error) {
var tlsConfig tls.Config
tlsConfig.ServerName = serverName
tlsConfig.InsecureSkipVerify = true
tlsConfig.NextProtos = []string{http3.NextProtoH3}
tlsConfig.Certificates = []tls.Certificate{
{
Certificate: cert,
PrivateKey: privateKey,
},
}
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
for _, curve := range options.CurvePreferences {
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve))
}
if !options.Insecure {
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return nil
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return err
}
if _, ok := cert.PublicKey.(*ecdsa.PublicKey); !ok {
return x509.ErrUnsupportedAlgorithm
}
if !cert.PublicKey.(*ecdsa.PublicKey).Equal(peerPublicKey) {
return x509.CertificateInvalidError{Cert: cert, Reason: 10, Detail: "remote endpoint has a different public key than what we trust in config.json"}
}
return nil
}
}
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.KernelRx || options.KernelTx {
if !C.IsLinux {
return nil, E.New("kTLS is only supported on Linux")
}
config = &KTLSClientConfig{
Config: config,
logger: logger,
kernelTx: options.KernelTx,
kernelRx: options.KernelRx,
}
}
return config, nil
}

View File

@@ -1,135 +0,0 @@
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"os"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
func NewOpenVPNClient(ctx context.Context, logger logger.ContextLogger, options option.OpenVPNTLSOptions) (Config, error) {
ca := options.CA
if ca == "" && options.CAPath != "" {
data, err := os.ReadFile(options.CAPath)
if err != nil {
return nil, E.Cause(err, "read ca_path")
}
ca = string(data)
}
certificate := options.Certificate
if certificate == "" && options.CertificatePath != "" {
data, err := os.ReadFile(options.CertificatePath)
if err != nil {
return nil, E.Cause(err, "read certificate_path")
}
certificate = string(data)
}
key := options.Key
if key == "" && options.KeyPath != "" {
data, err := os.ReadFile(options.KeyPath)
if err != nil {
return nil, E.Cause(err, "read key_path")
}
key = string(data)
}
if strings.TrimSpace(ca) == "" {
return nil, E.New("openvpn: missing ca certificate")
}
if block, _ := pem.Decode([]byte(ca)); block == nil {
return nil, E.New("openvpn: ca is not valid PEM")
}
hasCert := strings.TrimSpace(certificate) != "" || strings.TrimSpace(key) != ""
if hasCert {
if strings.TrimSpace(certificate) == "" || strings.TrimSpace(key) == "" {
return nil, E.New("openvpn: certificate and key must both be set")
}
if block, _ := pem.Decode([]byte(certificate)); block == nil {
return nil, E.New("openvpn: certificate is not valid PEM")
}
if block, _ := pem.Decode([]byte(key)); block == nil {
return nil, E.New("openvpn: key is not valid PEM")
}
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM([]byte(ca)) {
return nil, E.New("openvpn: failed to parse ca certificate")
}
var tlsConfig tls.Config
tlsConfig.RootCAs = roots
tlsConfig.InsecureSkipVerify = true
if options.CipherSuites != nil {
find:
for _, cipherSuite := range options.CipherSuites {
for _, tlsCipherSuite := range tls.CipherSuites() {
if cipherSuite == tlsCipherSuite.Name {
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
continue find
}
}
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
tlsConfig.VerifyConnection = func(cs tls.ConnectionState) error {
if len(cs.PeerCertificates) == 0 {
return E.New("openvpn: server did not provide certificate")
}
cert := cs.PeerCertificates[0]
intermediates := x509.NewCertPool()
for _, intermediate := range cs.PeerCertificates[1:] {
intermediates.AddCert(intermediate)
}
_, err := cert.Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
})
if err != nil {
return err
}
if options.VerifyX509Name != "" {
cn := cert.Subject.CommonName
switch options.VerifyX509NameMode {
case "name-prefix":
if !strings.HasPrefix(cn, options.VerifyX509Name) {
return E.New("openvpn: server CN ", cn, " does not match prefix ", options.VerifyX509Name)
}
case "name-suffix":
if !strings.HasSuffix(cn, options.VerifyX509Name) {
return E.New("openvpn: server CN ", cn, " does not match suffix ", options.VerifyX509Name)
}
default:
if cn != options.VerifyX509Name {
return E.New("openvpn: server CN ", cn, " does not match ", options.VerifyX509Name)
}
}
}
return nil
}
if hasCert {
cert, err := tls.X509KeyPair([]byte(certificate), []byte(key))
if err != nil {
return nil, E.Cause(err, "openvpn: parse client certificate/key")
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
var config Config = &STDClientConfig{ctx, &tlsConfig, false, 0, false}
if options.KernelRx || options.KernelTx {
if !C.IsLinux {
return nil, E.New("kTLS is only supported on Linux")
}
config = &KTLSClientConfig{
Config: config,
logger: logger,
kernelTx: options.KernelTx,
kernelRx: options.KernelRx,
}
}
return config, nil
}

View File

@@ -267,8 +267,8 @@ type realityVerifier struct {
}
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
p, _ := reflect.TypeFor[utls.Conn]().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Add(unsafe.Pointer(c.Conn), p.Offset))
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey)
h.Write(pub)

View File

@@ -141,14 +141,13 @@ func (c *STDServerConfig) startWatcher() error {
func (c *STDServerConfig) certificateUpdated(path string) error {
if path == c.certificatePath || path == c.keyPath {
switch path {
case c.certificatePath:
if path == c.certificatePath {
certificate, err := os.ReadFile(c.certificatePath)
if err != nil {
return E.Cause(err, "reload certificate from ", c.certificatePath)
}
c.certificate = certificate
case c.keyPath:
} else if path == c.keyPath {
key, err := os.ReadFile(c.keyPath)
if err != nil {
return E.Cause(err, "reload key from ", c.keyPath)
@@ -164,7 +163,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
config.Certificates = []tls.Certificate{keyPair}
c.config = config
c.access.Unlock()
c.logger.Notice("reloaded TLS certificate")
c.logger.Info("reloaded TLS certificate")
} else if common.Contains(c.clientCertificatePath, path) {
clientCertificateCA := x509.NewCertPool()
var reloaded bool
@@ -188,7 +187,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
config.ClientCAs = clientCertificateCA
c.config = config
c.access.Unlock()
c.logger.Notice("reloaded client certificates")
c.logger.Info("reloaded client certificates")
} else if path == c.echKeyPath {
echKey, err := os.ReadFile(c.echKeyPath)
if err != nil {
@@ -198,7 +197,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
if err != nil {
return err
}
c.logger.Notice("reloaded ECH keys")
c.logger.Info("reloaded ECH keys")
}
return nil
}
@@ -339,10 +338,9 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
}
tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePublicKeySHA256) > 0 {
switch tlsConfig.ClientAuth {
case tls.RequireAndVerifyClientCert:
if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
tlsConfig.ClientAuth = tls.RequireAnyClientCert
case tls.VerifyClientCertIfGiven:
} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven {
tlsConfig.ClientAuth = tls.RequestClientCert
}
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {

View File

@@ -1,78 +0,0 @@
package common
import (
"encoding/base64"
"encoding/json"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/sagernet/sing/common/json/badoption"
)
func StringToType[T any](str string) T {
var value T
v := reflect.ValueOf(&value).Elem()
switch any(value).(type) {
case badoption.Duration:
d, err := time.ParseDuration(str)
if err != nil {
v.SetInt(StringToType[int64](str))
} else {
v.Set(reflect.ValueOf(d))
}
return value
case badoption.HTTPHeader:
headers := badoption.HTTPHeader{}
reg := regexp.MustCompile(`^[ \t]*?(\S+?):[ \t]*?(\S+?)[ \t]*?$`)
for _, header := range strings.Split(str, "\n") {
result := reg.FindStringSubmatch(header)
if result != nil {
key := result[1]
headers[key] = strings.Split(result[2], ",")
}
}
v.Set(reflect.ValueOf(headers))
return value
}
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, _ := strconv.ParseInt(str, 10, 64)
v.SetInt(i)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i, _ := strconv.ParseUint(str, 10, 64)
v.SetUint(i)
case reflect.Float32, reflect.Float64:
f, _ := strconv.ParseFloat(str, 64)
v.SetFloat(f)
case reflect.Bool:
b, _ := strconv.ParseBool(str)
v.SetBool(b)
default:
panic("unsupported type")
}
return value
}
func DecodeBase64URLSafe(content string) (string, error) {
s := strings.ReplaceAll(content, " ", "-")
s = strings.ReplaceAll(s, "/", "_")
s = strings.ReplaceAll(s, "+", "-")
s = strings.ReplaceAll(s, "=", "")
result, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return content, nil
}
return string(result), nil
}
func ParseXHTTPRange(value string) (badoption.Range[int], error) {
result := badoption.Range[int]{}
encoded, err := json.Marshal(value)
if err != nil {
return result, err
}
return result, result.UnmarshalJSON(encoded)
}

View File

@@ -38,8 +38,8 @@ func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
n := len(dest)
if n > 0 && !dest[n-1].IsFull() {
nBytes, _ := dest[n-1].Write(src)
if n > 0 && !(dest)[n-1].IsFull() {
nBytes, _ := (dest)[n-1].Write(src)
src = src[nBytes:]
}

View File

@@ -0,0 +1,83 @@
package badoption
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/sagernet/sing-box/common/xray/crypto"
E "github.com/sagernet/sing/common/exceptions"
)
type Range struct {
From int32 `json:"from"`
To int32 `json:"to"`
}
func (c *Range) Build() *Range {
return (*Range)(c)
}
func (c *Range) MarshalJSON() ([]byte, error) {
if c.From == c.To {
return json.Marshal(c.From)
}
return json.Marshal(fmt.Sprintf("%d-%d", c.From, c.To))
}
func (c *Range) UnmarshalJSON(content []byte) error {
var rangeValue struct {
From int32 `json:"from"`
To int32 `json:"to"`
}
var stringValue string
err := json.Unmarshal(content, &stringValue)
if err == nil {
parts := strings.Split(stringValue, "-")
if len(parts) != 2 {
from, err := strconv.ParseInt(parts[0], 10, 32)
if err != nil {
return err
}
rangeValue.From, rangeValue.To = int32(from), int32(from)
} else {
from, err := strconv.ParseInt(parts[0], 10, 32)
if err != nil {
return err
}
to, err := strconv.ParseInt(parts[1], 10, 32)
if err != nil {
return err
}
rangeValue.From, rangeValue.To = int32(from), int32(to)
}
} else {
var int32Value int32
err := json.Unmarshal(content, &int32Value)
if err == nil {
rangeValue.From, rangeValue.To = int32Value, int32Value
} else {
err := json.Unmarshal(content, &rangeValue)
if err != nil {
return err
}
}
}
if rangeValue.From > rangeValue.To {
return E.New("invalid range")
}
*c = Range{rangeValue.From, rangeValue.To}
return nil
}
func (c *Range) String() string {
if c.From == c.To {
return strconv.FormatInt(int64(c.From), 10)
}
return fmt.Sprintf("%d-%d", c.From, c.To)
}
func (c Range) Rand() int32 {
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
}

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